一 使用strace命令跟踪系统调用
使用方法
- 打补丁
patch -p1 < ../xxx.patch
- 配置strace && 编译
./configure --host=arm-linux CC=arm-linux-gcc
make
编译完成之后得到一个strace应用程序,拷贝到开发板上就可以使用了
使用方法:
./strace -o log.txt ./firstdrvtext on
log.txt
是调试信息输出的文件,./firstdrvtext on
是应用程序使用方法
简单介绍strace的原理
./strace(父进程) -o log.txt ./firstdrvtext on(子进程)
父进程启动一个子进程,子进程先判断是否被跟踪,如果是,则会发送给父进程一些信号,然后进入休眠,父进程根据信号值决定记录哪些数据,父进程处理完之后(如记录),子进程继续运行。
子进程里面的open,read,write(c库)函数会调用一条swi指令 swi val
open —- swi #val1
read —- swi #val2
write —- swi #val3
这条指令会导致系统swi异常,进入内核态,调用sys_open,sys_read …
- 打开内核文件
arch/arm/kernel/entry-common.s
:
...
tst ip, #_TIF_SYSCALL_TRACE @ are we tracing syscalls?
bne __sys_trace
// 测试标记为,看是否被跟踪,如果被跟踪,则调用 __sys_trace
...
- __sys_trace:
/*
* This is the really slow path. We're going to be doing
* context switches, and waiting for our parent to respond.
*/
__sys_trace:
mov r2, scno
add r1, sp, #S_OFF
mov r0, #0 @ trace entry [IP = 0]
bl syscall_trace
adr lr, __sys_trace_return @ return address
mov scno, r0 @ syscall number (possibly new)
add r1, sp, #S_R0 + S_OFF @ pointer to regs
cmp scno, #NR_syscalls @ check upper syscall limit
ldmccia r1, {r0 - r3} @ have to reload r0 - r3
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
b 2b
strace的其他用法
二 使用GDB调试应用程序
编译与安装
- 配置 && 编译 && 安装
./configure --target=arm-linux --disable-werror
- 查看安装目录
vi makefile
prefix = /usr/local
- 安装目录修改当前目录下的tmp
mkdir tmp
make install prefix=$PWD/tmp
- 配置 && 编译gdbserver,并复制到网络文件系统
cd gdb/gdbserver
./configure --host=arm-linux
make
cp gdbserver /work/fs.. /bin/gdbserver
GDB远程调试步骤
编译要调试的应用程序,编译时要加上-g选项
- 在ARM板上
gdbserver 192.168.1.17:2345 ./test_debug
// ip是arm板的ip,端口随便写
- 在pc机上
/bin/arm-linux-gdb ./test_debug
target remote 192.168.1.17:2345
- 常用命令:
l: 查看源码
break main / break test_debug:31: 打断点 //main函数打断点 / 31行打断点
c:继续执行
step :单步调试
print *p:输出
quit:退出
...
GDB的另一种使用方法
让程序在开发板上直接运行,当它发送错误时,令它产生core dump文件
然后使用gdb根据core dump文件找到发生错误的地方。
- 在开发板上输入
ulimit -c unlimited
执行应用程序
./test_debug
程序出错会在当前目录下生成名为core
的文件拷贝core文件到pc上,pc机上执行:
cp core /work/xxx/core
/bin/arm-linux-gdb ./test_debug ./core
若程序出错,就可以看到很多调试信息。
三 配置内核,输出应用程序的段错误信息
配置内核,输出错误信息
- 根据应用程序报错的信息在内核源码中搜索:
grep "Unable to handle kernel" * -nR
- 找到并打开
arch/arm/mm/fault.c
,看到下面的函数:
/* 处理应用程序错误函数 */
static void
__do_user_fault(struct task_struct *tsk, unsigned long addr,
unsigned int fsr, unsigned int sig, int code,
struct pt_regs *regs)
{
struct siginfo si;
unsigned long ret;
unsigned long val;
int i =0;
#ifdef CONFIG_DEBUG_USER // 1 配置内核
if (user_debug & UDBG_SEGV) { // 2 设置启动参数
printk(KERN_DEBUG "%s: unhandled page fault (%d) at 0x%08lx, code 0x%03x\n",
tsk->comm, sig, addr, fsr);
show_pte(tsk->mm, addr);
show_regs(regs);
/* 开始打印栈信息 */
printk("Stack: \n");
while(i<1024)
{
if(copy_from_user(&val,(const void __user *)(regs->ARM_sp+i*4),4)
break;
printk("%08x ",val);
i++;
if(i % 8 == 0)
printk("\n");
}
printk("\nEnd of stack \n");
/* 打印栈信息结束 */
}
#endif
tsk->thread.address = addr;
tsk->thread.error_code = fsr;
tsk->thread.trap_no = 14;
si.si_signo = sig;
si.si_errno = 0;
si.si_code = code;
si.si_addr = (void __user *)addr;
force_sig_info(sig, &si, tsk);
}
- 配置内核 CONFIG_DEBUG_USER
make menuconfig
/DEBUG_USER // 搜索,配置成y
make uImage
- uboot设置启动参数 user_debug
set bootargs user_debug=0xff ... ...
- 执行./test_debug,可以看到调试信息和寄存器的值
- 反汇编app
arm-linux-objdump -D test_debug > test_debug.dis
在反汇编中找到pc值处的代码,根据寄存器的值找到程序出错的原因
修改内核,打印栈信息
struct pt_regs
包含发生错误的瞬间所有寄存器的值:
struct pt_regs {
long uregs[18];
};
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]
在arch/arm/mm/__do_user_fault
中加入下面的代码:
unsigned long ret;
unsigned long val;
int i =0;
printk("Stack: \n");
while(i<1024)
{
/* 从堆栈指针sp处开始打印栈信息 */
if(copy_from_user(&val,(const void __user *)(regs->ARM_sp+i*4),4)
break;
printk("%08x ",val);
i++;
if(i % 8 == 0)
printk("\n");
}
printk("\nEnd of stack \n");
- 重新编译内核,复制到网络文件系统,并用新内核启动
make uImage
cp arch/arm/uImage /work/nfs_root/fs_mini_mdev/uImage
reboot
nfs 32000000 192.168.1.123:/work/.../uImage_new
main函数被动态库调用,怎么知道动态库的地址?
- 方法1
ps // 查看进程ID
cat /proc/xxx(进程ID)
cat maps // 查看应用程序里面各段地址
- 方法2,使用gdb调试,先启动gdb,使用命令
info file
查看程序里面的各个段
动态链接不容易看程序调用信息,把应用程序编译成静态链接的方式:
arm-linux-gcc -o test test.c -static
四 自制系统调用
ps:这种方法很少用,也比较繁琐,适合长时间打印调试信息
原理简介
app 调用open read write
函数,导致:swi #val
程序跳转到vector_swi
处去执行,得到“导致异常的指令”,取出里面的val,根据这个val调用对应的处理函数,如sys_open,sys_write,sys_read...
这些函数在一个数组里,通过查找数组调用对应的函数。
- 仿照这种形式写一个自己的系统调用
自制系统调用
- 写一个应用函数,仿glibc,用来触发系统调用
仿照glibc里面的brk.c里面的_brk函数(c嵌入汇编的形式)
#include <errno.h>
#include <unistd.h>
#define __NR_OABI_SYSCALL_BASE 0x900000
//#if defined(__thumb__) || defined(__ARM_EABI__)
//#define __NR_SYSCALL_BASE 0
//#else
#define __NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE
//#endif
void hello(char *buf ,int count )
{
/* swi */
asm ("mov r0, %0\n" /* save the argment in r0 */
"mov r1, %1\n" /* save the argment in r0 */
"swi %2\n" /* do the system call */
:
: "r"(buf),"r"(count), "i" (__NR_SYSCALL_BASE + 352)
: "r0","r1"); /* 过程中会改变的寄存器 */
}
void main(int argc , char ** argv)
{
printf(" in app, call hello \n");
/* 执行hello函数触发一个swi指令异常 */
hello("tjpuzh.top",10);
return 0;
}
- 修改内核,添加系统调用函数的声明和实现
- 声明:
include\linux\syscalls.h中加入
asmlinkage ssize_t sys_hello(const char __user *buf,int count);
- 实现:
在\fs\read_write.c中实现sys_hello
asmlinkage ssize_t sys_hello(const char __user * buf,int count)
{
... ...
// 此处实现想要实现的功能,如open,read,write...
}
- 添加
在 arch\arm\kernel\calls.S中,最后加入一项CALL(sys_hello)
- 测试
make uImage
cp arch/arm/uImage /work/nfs_root/fs_mini_mdev/uImage
reboot
nfs 32000000 192.168.3.123:/work/nfs_root/fs_mini_mdev/uImage_hello
bootm 32000000
使用自制系统调用来打输出调试信息
整体流程
- 修改应用程序的可执行文件,替换“某个位置”的代码为swi val
- 执行程序
- 执行到断点处,进入sys_hello
- 在sys_hello,打印调试信息,执行原来的指令,返回
测试步骤
- 写一个简单的应用程序 :
debug_with_system_call.c
- 替换
- 输出反汇编文件
arm-linux-objdump -D debug_with_system_call > debug_with_system_call.dis
根据适合打断点的指令,找到合适机器码
反汇编自制系统调用的测试程序,可以看到swi指令的机器码
arm-linux-objdump -D test_system_call > test_system_call.dis
- 修改应用程序的机器码为swi指令的机器码
保存为
debug_with_system_call_swi
加上执行权限:
sudo chmod +x debug_with_system_call_swi
- 修改自制的sys_hello函数
#include <asm/processor.h>
asmlinkage ssize_t sys_hello(const char __user * buf,int count)
{
int val;
struct pt_regs *regs;
/* 1 输出一些打印调试信息 */
/* 应用程序的反汇编里:00010788 <cnt> */
copy_from_user(&val,(const void __user *)0x00010788,4);
printk("sys_hello:val = %d\n",val);
/* 2 执行被替换的指令: add r3, r3, #2 ; 0x2 */
// 搜索pt_regs,在它的结果里再搜索current
regs = task_pt_regs(current);
regs->ARM_r3 += 2;
/* 3 反回 */
return;
}
- 运行
./debug_with_system_call_swi
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!