21_应用调试方法
文章目录
1、使用strace命令来跟踪系统调用
1.1、体验strace简单操作
1、解压缩:tar xjf strace-4.5.15.tar.bz2
进入strace目录:cd strace-4.5.15/
打补丁:patch -p1 < …/strace-fix-arm-bad-syscall.patch
/* host:表示编译后在那里运行;CC:表示使用什么编译器 */
编译strace:./configure --host=arm-linux CC=arm-linux-gcc
make
复制到根文件系统中:cp strace /work/nfs_root/first_fs
2、开发板上装载驱动、使用strace跟踪系统调用:
strace命令可以看到应用程序运行过程中调用了哪些系统调用:
3、放入Ubuntu的/work/system中:
在网站https://busybox.net/downloads/下载busybox-1.20.0.tar.bz2,
解压:tar xjf busybox-1.20.0.tar.bz2
进入目录:cd busybox-1.20.0/
配置:make menuconfig,添加arm-linux-
Busybox Settings --->
Build Options --->
(arm-linux-) Cross Compiler prefix
4、执行:make
出现如下错误:
解决办法:去掉ionice
make menuconfig之后按”/”查找ionice发现在下列目录中:
输入n去除:
5、再次make:出错
解决办法:添加mtd/mtd-user.h头文件、去掉nandwrite
顺便将nanddump也去掉。
6、再次make:出错
解决办法:将ubi去掉
7、再次make:成功
进入根文件目录:cd /work/nfs_root/first_fs/
备份busy_1.7.0:cp bin/busybox bin/busybox_1.7.0
拷贝新的busybox:sudo cp /work/system/busybox-1.20.0/busybox /bin/busybox
8、重启开发板:reboot
运行新的busybox:busybox是新的1.20.0
9、来装载驱动、卸载驱动:发生错误
这时候就可以使用strace来调试:
strace -o log.txt rmmod first_drv
查看log.txt:发现没有modules
10、来创建modules:
mkdir /lib/modules
再来执行rmmod,出错:
11、再来执行strace:
strace -o log.txt rmmod first_drv:发现没有目录2.6.22.6
12、来创建2.6.22.6目录:
mkdir /lib/modules/2.6.22.6
这样卸载驱动时就成功了。
1.2、strace命令原理
strace -o log.txt rmmod first_drv
strace为父进程,会启动一个子进程(rmmod first_drv),子进程涉及的系统调用都是一条swi指令,导致cpu发生异常,进入swi的异常处理函数中;
在异常处理函数中先判断这个进程是否被跟踪,若被跟踪就会给父进程发一个信号之后就进入休眠;
父进程根据这个信号可以做一些事情:例如根据异常swi #val值知道是哪一个系统调用,把发生的时间、参数等等记录下来,之后再让子进程停止休眠,继续执行。
2、使用GDB来调试应用程序
在window上使用VC++ 6.0可以调试源码;在linux下使用gdb来调试源码。
gdb调试原理:
从http://ftp.gnu.org/gnu/gdb/上下载gdb-7.4.tar.gz(或者gdb-7.4.tar.bz2)压缩文件,放入ubuntu的/work/debug目录中去。
2.1、编译gdb、gdbserver
解压:tar xjf gdb-6.7.tar.bz2 (或者tar -zxvf gdb-7.4.tar.gz)
进入目录:cd gdb-7.4/
配置:./configure --target=arm-linux --prefix=
P
W
D
/
t
m
p
−
v
m
a
k
e
:
出
错
,
m
k
d
i
r
t
m
p
m
a
k
e
i
n
s
t
a
l
l
p
r
e
f
i
x
=
PWD/tmp -v make:出错, mkdir tmp make install prefix=
PWD/tmp−vmake:出错,mkdirtmpmakeinstallprefix=PWD/tmp :出错
解决办法
修改为:
/work/debug/gdb-7.4$ ./configure --target=arm-linux --disable-werror --prefix=$PWD/tmp -v
再来make 成功。
把arm-linux-gdb复制到/bin目录:
cd gdb/gdbserver/
./configure --host=arm-linux
make:出错–宏未定义
进入目录搜索宏:
cd /work/tools/gcc-3.4.5-glibc-2.3.6/
grep “PTRACE_GETSIGINFO” * -nR
回到之前目录:cd -
修改Makefile:vi linux-arm-low.c
添加#include<linux/ptrace.h>
再来make
出现gdbserver文件:
再来执行:cp gdbserver /work/nfs_root/first_fs/bin
2.2、编译要调试的应用
编译时加上-g选项:
arm-linux-gcc -g -o test_debug test_debug.c
cp test_debug /work/nfs_root/first_fs
2.3、测试gdbserver
2.3.1、编写测试程序
test_debug.c:
#include <stdio.h>
void C(int *p)
{
*p = 0x12;
}
void B(int *p)
{
C(p);
}
void A(int *p)
{
B(p);
}
void A2(int *p)
{
C(p);
}
int main(int argc, char **argv)
{
int a;
int *p = NULL;
A2(&a); // A2 > C
printf("a = 0x%x\n", a);
A(p); // A > B > C
return 0;
}
2.3.2、调试
2.3.2.1、第一种调试方法
运行测试程序,发生段错误:
-
在ARM板上
gdbserver 192.168.2.55:2345 ./test_debug -
在PC上
/bin/arm-linux-gdb ./test_debug
输入:target remote 192.168.2.55:2345
然后: 使用gdb命令来控制程序
输入:l (小写的L) (查看源码)
输入:break main (打断点)
c
break test_debug.c:32
c
step
step
print *p
step
print *p
c
step
step
quit
y
2.3.2.2、第二种调试方法
让程序在开发板上直接运行,当它发生错误时,令它产生core dump文件
然后使用gdb根据core dump文件找到发生错误的地方
在ARM板上:
- ulimit -c unlimited
- 执行应用程序./test_debug : 程序出错时会在当前目录下生成名为core的文件
在PC上:
28th_app_debug$ sudo cp /work/nfs_root/first_fs/core .
3. 28th_app_debug$ sudo /bin/arm-linux-gdb ./test_debug ./core
输入bt命令查看调用信息:
3、配置内核输出应用程序的段错误信息
3.1、与驱动的段错误信息比较、介绍用户程序的段错误
原先调试第25个驱动程序./firstdrvtest on时,程序运行出错会打印断的错误信息:
但是在调试应用程序./test_debug时只打印的一些,并没有打印更多的信息。
现在来修改内核配置,使其能够打印更多的信息。
搜索出错信息:Unable to handle kernel
在内核目录下搜索:
进入arch目录搜索:
在fault.c的93行:
在文件中有函数:
其中do_user_fault函数:
条件成立的两个变量为1:UDBG_SEGV 2:user_debug
3.2、配置内核打印出段错误信息
3.2.1、配置内核
在文件arch/arm/mm/fault.c中有:DEBUG_USER
__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;
#ifdef CONFIG_DEBUG_USER // 1、配置内核
if (user_debug & UDBG_SEGV) {
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);
}
#endif
在内核中:make menuconfig
搜索DEBUG_USER:
在Kernel hacking中:
在Kernel hacking的[] Verbose user fault messages / 显示用户的错误信息 */
3.2.2、设置启动参数user_debug
uboot: set bootargs user_debug=0xff (0xff与其他变量相与都成立,所有的用户信息都打印出来)
重启开发板,设置启动参数:user_debug=0xff
set bootargs noinitrd root=/dev/nfs nfsroot=192.168.2.16:/work/nfs_root/first_fs ip=192.168.2.55:192.168.2.16:192.168.2.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0 user_debug=0xff
3.2.3、执行app
如./test_debug 若会打印定时消息,把目录linux-2.6.22.6\arch\arm\kernel\中irq.c内添加的打印信息去掉。
3.2.4、反汇编app
arm-linux-objdump -D test_debug > test_debug.dis
再把反汇编文件复制到window里。
在反汇编文件里搜索出错时pc的值84ac:
在84ac行:把r3里的值0x12存入r2寄存器0中去出错,因为r2所指向的为空指针。
3.2.5、内核中搜索栈信息和添加栈打印信息
但是打印的信息没有栈信息
在目录中/work/system/linux-2.6.22.6/arch/arm$搜索grep "Stack: " * -nR
搜索到在kernel/traps.c的229行有相关信息
来修改内核,添加代码,在目录\linux-2.6.22.6_project\linux-2.6.22.6\arch\arm\mm\的fault.c中:
unsigned long ret; //zdy
unsigned long val; //zdy
int i = 0; //zdy
。。。
。。。
/* ---zdy---down */
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("\n End of Stack\n");
/* ---zdy---up */
3.2.6、重新编译内核
再来重新编译内核:make uImage
再将uImage拷贝到文件系统中:cp arch/arm/boot/uImage /work/nfs_root/uImage_user
开发板从网络文件系统启动:nfs 32000000 192.168.2.16:/work/nfs_root/uImage_user;bootm 32000000
再次运行app,如./test_debug,就可以看见栈信息:
3.2.6.1、动态链接编译的app:test_debug
使用动态链接编译:arm-linux-gcc -g -o test_debug test_debug.c
运行:
/ # ./test_debug
a = 0x12
pgd = c3d1c000
[00000000] *pgd=33d0a031, *pte=00000000, *ppte=00000000
Pid: 765, comm: test_debug
CPU: 0 Not tainted (2.6.22.6 #24)
PC is at 0x84ac
LR is at 0x84d0
pc : [<000084ac>] lr : [<000084d0>] psr: 60000010
sp : be8bae60 ip : be8bae74 fp : be8bae70
r10: 4013365c r9 : 00000000 r8 : 00008514
r7 : 00000001 r6 : 000085cc r5 : 00008568 r4 : be8baee4
r3 : 00000012 r2 : 00000000 r1 : 00001000 r0 : 00000000
Flags: nZCv IRQs on FIQs on Mode USER_32 Segment user
Control: c000717f Table: 33d1c000 DAC: 00000015
[<c002cd9c>] (show_regs+0x0/0x4c) from [<c0031aa0>] (__do_user_fault+0x64/0x144)
r4:c0494060
[<c0031a3c>] (__do_user_fault+0x0/0x144) from [<c0031dd8>] (do_page_fault+0x1dc/0x20c)
[<c0031bfc>] (do_page_fault+0x0/0x20c) from [<c002b2b4>] (do_DataAbort+0x3c/0xa0)
[<c002b278>] (do_DataAbort+0x0/0xa0) from [<c002bec8>] (ret_from_exception+0x0/0x10)
Exception stack(0xc3d1bfb0 to 0xc3d1bff8)
bfa0: 00000000 00001000 00000000 00000012
bfc0: be8baee4 00008568 000085cc 00000001 00008514 00000000 4013365c be8bae70
bfe0: be8bae74 be8bae60 000084d0 000084ac 60000010 ffffffff
r8:00008514 r7:00000001 r6:000085cc r5:00008568 r4:c0399ee8
Stack:
00000000 be8bae84 be8bae74 000084d0 000084a0 00000000 be8bae98 be8bae88
C’s sp return addr B’s sp
000084f0 000084c4 00000000 be8baeb8 be8bae9c 00008554 000084e4 00000000
return addr A’s sp return addr main’s sp
00000012 be8baee4 00000001 00000000 be8baebc 40034f14 00008524 00000000
return addr caller’s sp
对于动态链接,已经推出的程序不好确定动态库的地址
00000000 0000839c 00000000 00000000 4001d594 000083c4 000085cc 4000c02c
be8baee4 be8baf8f 00000000 be8baf9c be8bafa6 be8bafad be8bafb8 be8bafdb
。。。
。。。
69622f3d 68732f6e 44575000 2e002f3d 7365742f 65645f74 00677562 00000000
End of Stack
Segmentation fault
main调用A,A调用B,B调用C,程序在C函数里执行到pc : [<000084ac>]时出错。
我们查看main函数的调用者时发现反汇编文件中找不到,因为使用的时动态库,
问:如何知道动态库在哪里?
答1: ps:查看进程号为764
答2:使用gdb
在开发板运行:gdbserver 192.168.2.55:2345 ./test_debug
在ubuntu的28th_app_debug目录运行:/bin/arm-linux-gdb ./test_debug
再输入:target remote 192.168.2.55:2345
开发板显示:
gdb运行:info file
在里面没有找到main函数的返回地址40034f14相近的值;
对于动态链接,已经推出的程序不好确定动态库的地址。
3.2.6.2、静态链接编译的app:test_debug
重新编译程序,使用静态链接:
运行测试程序:./test_debug
反汇编测试程序:arm-linux-objdump -D test_debug > test_debug_static.dis
对于静态链接的test_debug:
运行:
/ # ./test_debug
a = 0x12
pgd = c3f2c000
[00000000] *pgd=33ee6031, *pte=00000000, *ppte=00000000
Pid: 772, comm: test_debug
CPU: 0 Not tainted (2.6.22.6 #25)
PC is at 0x81e0
LR is at 0x8204
pc : [<000081e0>] lr : [<00008204>] psr: 60000010
sp : befb3c90 ip : befb3ca4 fp : befb3ca0
r10: 000085f4 r9 : 00008248 r8 : befb3ee4
r7 : 00000001 r6 : 00000000 r5 : befb3d6e r4 : 00000000
r3 : 00000012 r2 : 00000000 r1 : 00001000 r0 : 00000000
Flags: nZCv IRQs on FIQs on Mode USER_32 Segment user
Control: c000717f Table: 33f2c000 DAC: 00000015
[<c002cd1c>] (show_regs+0x0/0x4c) from [<c0031aa0>] (__do_user_fault+0x64/0x144)
r4:c04b6d20
[<c0031a3c>] (__do_user_fault+0x0/0x144) from [<c0031dd8>] (do_page_fault+0x1dc/0x20c)
[<c0031bfc>] (do_page_fault+0x0/0x20c) from [<c002b224>] (do_DataAbort+0x3c/0xa0)
[<c002b1e8>] (do_DataAbort+0x0/0xa0) from [<c002be48>] (ret_from_exception+0x0/0x10)
Exception stack(0xc3ed3fb0 to 0xc3ed3ff8)
3fa0: 00000000 00001000 00000000 00000012
3fc0: 00000000 befb3d6e 00000000 00000001 befb3ee4 00008248 000085f4 befb3ca0
3fe0: befb3ca4 befb3c90 00008204 000081e0 60000010 ffffffff
r8:befb3ee4 r7:00000001 r6:00000000 r5:befb3d6e r4:c0399ec8
Stack:
00000000 befb3cb4 befb3ca4 00008204 000081d4 00000000 befb3cc8 befb3cb8
C'sp ret addr B'sp
00008224 000081f8 00000000 befb3ce8 befb3ccc 00008288 00008218 00000000
ret addr A'sp ret addr main'sp
00000012 befb3ee4 00000001 00000000 befb3cec 000084ac 00008258 756e694c
ret addr __libc_start_main'sp
00000078 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 32393100
。。。
。。。
。。。
752f3a6e 622f7273 53006e69 4c4c4548 69622f3d 68732f6e 44575000 2e002f3d
7365742f 65645f74 00677562 00000000
End of Stack
Segmentation fault
4、自制系统调用、编写进程查看器
4.1、系统调用原理
4.1.1、原理图
4.1.2、代码分析
在entry-common.S (linux-2.6.22.6\arch\arm\kernel)中:
保存现场:
在内核中搜索宏,查看是否被配置:若无则不需要看
执行命令:vi .config
例如执行/ CONFIG_ARM_THUMB搜索
发现没有配置则不需要看;再查看其他宏是否配置。
分辨是否为swi指令:
存放系统调用的数组:
从上述数组中取哪一项呢?
清除高8位,只留下val值:
根据table和val值就可以找到对应的函数:
4.2、修改内核:仿照sys_write
4.2.1、修改calls.S在数组中添加sys_hello
在目录\linux-2.6.22.6\arch\arm\kernel\calls.S中仿照sys_write把函数sys_hello放入数组中
名字为sys_hello:
4.2.2、修改read_write.c定义sys_hello函数
在目录linux-2.6.22.6\fs\read_write.c下定义sys_hello函数
4.2.3、在syscalls.h文件中声明sys_hello函数
在目录\linux-2.6.22.6\include\linux\ syscalls.h中声明编写的sys_hello函数
4.3、编写应用程序:仿照glibc
4.3.1、分析glibc程序
4.3.2、编写测试程序
在目录\29th_app_system_call下编写test_system_call.c文件
4.4、运行测试程序
1、main函数调用hello函数;
2、hello函数最终指向swi指令,进入内核态调用sys_hello函数;
3、sys_hello函数从用户空间把数据拷贝到内核空间,然后打印出来。
把29th_app_system_call\kernel里的文件复制到内核目录:
syscalls.h ==> include/linux
read_write.c ==> fs/
calls.S ==> arch/arm/kernel
重新编译内核:
make uImage
cp arch/arm/boot/uImage /work/nfs_root/uImage_sys_hello
编译测试程序:
arm-linux-gcc -o test_system_call test_system_call.c
cp test_system_call /work/nfs_root/first_fs
运行测试程序:
1、使用老内核运行./test_system_call
2、使用新内核运行./test_system_call
4.5、使用自制系统调用程序调试
4.5.1、介绍
4.5.2、第1个程序:正常执行命令
1、编写test_sc.c测试程序
#include <stdio.h>
int cnt = 0;
void C(void)
{
int i = 0;
while (1)
{
printf("Hello, cnt = %d, i = %d\n", cnt, i);
cnt++;
i = i + 2;
}
}
void B(void)
{
C();
}
void A(void)
{
B();
}
int main(int argc, char **argv)
{
A();
return 0;
}
2、编译测试程序test_sc.c
arm-linux-gcc -o test_sc test_sc.c
cp test_sc /work/nfs_root/first_fs
3、运行测试程序./test_sc
4.5.3、第2个程序:内核打印错误信息
4、反汇编可执行程序test_sc
arm-linux-objdump -D test_sc > test_sc.dis
在pc机下查看反汇编文件:
在可执行程序中找到02 30 83 e2:
5、反汇编目录\29th_system_call下的可执行程序test_system_call
arm-linux-objdump -D test_system_call > test_system_call.dis
确定swi指令的机器码:
84b8: ef900160 swi 0x00900160
把test_sc中的02 30 83 e2替换成ef 90 01 60
把文件重命名为test_sc_swi上传到ubuntu。
6、修改sys_hello函数
在反汇编文件test_sc.dis中搜索cnt值:
在目录\linux-2.6.22.6\include\asm-arm\processor.h下找到宏task_pt_regs:
包含头文件:#include <asm/processor.h>
来修改sys_hello函数:
7、将文件拷贝到内核
把\kernel里的文件复制到内核目录:
//syscalls.h ==> include/linux
read_write.c ==> fs/
//calls.S ==> arch/arm/kernel
8、编译内核、重新启动开发板
make uImage
cp arch/arm/boot/uImage /work/nfs_root/uImage_sys_hello2
cp test_sc_swi /work/nfs_root/first_fs
重启开发板:
nfs 32000000 192.168.2.16:/work/nfs_root/uImage_sys_hello2;bootm 32000000
9、测试./test_sc_swi 出错:
添加权限重新运行:chmod =x ./test_sc_swi
10、打印太快,在test_sc.c中添加延时:
再次重新编译测试程序,并且重命名为test_sc_sleep.c,输出反汇编文件
arm-linux-gcc -o test_sc_sleep test_sc_sleep.c
arm-linux-objdump -D test_sc_sleep > test_sc_sleep.dis
cp test_sc_sleep /work/nfs_root/first_fs
再次运行测试程序test_sc_sleep:
这样就打印一句话5s之后才会打印下一句话。
11、再把test_sc_sleep.dis中的e2833002在test_sc_sleep中替换为00900160
在test_sc_sleep中:
将test_sc_sleep替换为test_sc_sleep_swi并且放入根文件系统,再加上可执行权限:
ubuntu:cp test_sc_sleep_swi /work/nfs_root/first_fs
开发板:chmod +x ./test_sc_sleep_swi
12、再来很执行./test_sc_sleep_swi
发现val一直为0
最终发现在sys_hello函数中:
将文件拷贝到内核,把\kernel里的文件复制到内核目录:
read_write.c ==> fs/
13、编译内核、重新启动开发板
make uImage
cp arch/arm/boot/uImage /work/nfs_root/uImage_sys_hello2
重启开发板:
nfs 32000000 192.168.2.16:/work/nfs_root/uImage_sys_hello2;bootm 32000000
14、再次测试./test_sc_sleep_swi
成功打印:
4.5.4、第3个程序:打印局部变量i
15、打印局部变量i
局部变量i的地址为:
修改sys_hello函数:添加打印i的代码
将文件拷贝到内核,把\kernel里的文件复制到内核目录:
read_write.c ==> fs/
16、编译内核、重新启动开发板
make uImage
cp arch/arm/boot/uImage /work/nfs_root/uImage_sys_hello2
重启开发板:
nfs 32000000 192.168.2.16:/work/nfs_root/uImage_sys_hello2;bootm 32000000
17、再次测试./test_sc_sleep_swi
5、输入模拟器
5.1、设计思路
1、产品要经过测试才能发布,一般都是人工操作,比如手机触摸屏、遥控器;
2、操作过程中发现错误,要再次复现,找到规律,修改程序;
3、能否在驱动程序里把所有的操作记录下来,存为文件,当出错时,可以通过文件里的数据来"复现"输入。
在输入子系统中使用input_event来上报事件,还可以把事件保存下来。
5.2、运行TS触摸屏的tslib测试程序
1、安装驱动
insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd.ko
insmod s3c_ts.ko
2、运行测试
ts_calibrate /* 校准测试 /
ts_test / 拖拽和写字测试 */
测试写字功能,再把测试结果保存下来,复现出之前的写字操作:
5.3、复现数据
5.3.1、框架
1、TS框架
2、复现函数框架
5.3.2、程序
引用:
https://blog.csdn.net/W1107101310/article/details/80790059
程序写作思路:使用输入子系统中的触摸屏程序来进行说明。因为触摸屏程序中有input_event函数。我们在使用input_event函数上报事件的同时使用我们前面写的myprintk函数来记录上报的内容。当记录完成后我们可以通过cat /proc/mymsg命令来查看我们所记录的内容。当需要复现的时候我们再通过input_event函数将记录的数据输出,做到操作的复现。
1、记录上报参数
我们看input_event函数,来了解我们都要上报什么信息:
/*
* input_event() - 上报新的输入事件
* @dev: 产生事件的设备
* @type: 事件类型
* @code: 具体事件码
* @value: 事件值
*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
{
······
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
case SYN_REPORT:
}
break;
case EV_KEY:
break;
case EV_SW:
break;
case EV_ABS:
break;
case EV_REL:
break;
case EV_MSC:
break;
case EV_LED:
break;
case EV_SND:
break;
case EV_REP:
break;
case EV_FF:
break;
}
}
从上面看input_event函数的参数有:dev,type,code,val。因为我们知道要记录的主要是事件的值,所以发生事件的设备就不记录了,取而代之的是我们要记录上报这个事件的时间time。这里我们使用jiffies来记录时间。所以我们要记录的内容为time,type,code,val。
同时由于我们要使用myprintk函数来将上报内容记录到mymsg中,所以在这个程序里我们要声明myprintk函数:
/* 声明自己写的myprintk打印函数 */
extern int myprintk(const char *fmt, …);
下面我们就要写一个记录函数来将这些重要的信息记录下来了。
void write_input_event_to_file(unsigned int time, unsigned int type, unsigned int code, int val)
{
myprintk("0x%08x 0x%08x 0x%08x %d\n", time, type, code, val);
}
而记录信息的格式为:
0x00076617 0x00000003 0x00000018 0
0x00076617 0x00000003 0x00000018 0
0x0007661b 0x00000003 0x00000018 0
0x0007661b 0x00000003 0x00000018 0
接下来我们就要到触摸屏程序中找上报事件了,我们找到上报事件后在其后加入report_to_printk函数,将上报的内容记录下来。例如:
input_report_abs(s3c_ts_dev,ABS_PRESSURE,0);
report_to_printk(jiffies,EV_ABS,ABS_PRESSURE,0);
input_report_key(s3c_ts_dev,BTN_TOUCH,0);
report_to_printk(jiffies,EV_KEY,BTN_TOUCH,0);
input_sync(s3c_ts_dev);
report_to_printk(jiffies,EV_SYN,SYN_REPORT,0);
input_report_abs(s3c_ts_dev,ABS_X,(x[0]+x[1]+x[2]+x[3])/4);
report_to_printk(jiffies,EV_ABS,ABS_X,(x[0]+x[1]+x[2]+x[3])/4);
input_report_abs(s3c_ts_dev,ABS_Y,(y[0]+y[1]+y[2]+y[3])/4);
report_to_printk(jiffies,EV_ABS,ABS_Y,(y[0]+y[1]+y[2]+y[3])/4);
input_report_abs(s3c_ts_dev,ABS_PRESSURE,1);
report_to_printk(jiffies,EV_ABS,ABS_PRESSURE,1);
input_report_key(s3c_ts_dev,BTN_TOUCH,1);
report_to_printk(jiffies,EV_KEY,BTN_TOUCH,1);
input_sync(s3c_ts_dev);
report_to_printk(jiffies,EV_SYN,SYN_REPORT,0);
修改完代码我们就可以装载这些驱动了。但是这里提醒一下,在装载驱动时,要先装载记录事件mymsg.ko的驱动,再装载触摸屏s3c_ts.ko的驱动。而如果先装载触摸屏的驱动就会出现:Unknown symbol myprintk 的错误提示而且不能装载这个驱动,这是因为我们在触摸屏中用到了myprintk函数,但是我们并没有定义它,所以我们要先装载记录事件mymsg.ko的驱动,再装载触摸屏s3c_ts.ko的驱动。
装载完两个驱动我们就可以调试通过cat /proc/mymsg命令查看记录信息,同时我们可以通过命令: cp /proc/mymsg /ts.txt 将记录的数据保存到一个文件中。
2、复现记录
现在我们要完成另一个功能:从文件中得到记录的数据,并上报这些数据以实现复现功能。因此我们要在触摸屏程序中再添加一个字符驱动程序来完成上面的功能。这个字符驱动程序中有:
write函数:将文件中的数据写入驱动程序的buf中。
ioctl函数:启动复现功能,同时加入标记功能来记录重要信息的位置。
我先将驱动框架写出:
int auto_major = 0;
static struct class *cls;
static ssize_t replay_write (struct file *file, const char __user *buf, size_t size, loff_t *loff_t)
{
return 0;
}
static int replay_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
static struct file_operations replay_fops = {
.owner = THIS_MODULE,
.write = replay_write,
.ioctl = replay_ioctl,
};
入口函数:
auto_major = register_chrdev(0,"replay",&replay_fops);
cls = class_create(THIS_MODULE,"replay");
device_create(cls,NULL,MKDEV(auto_major,0),"replay");
出口函数:
device_destroy(cls,MKDEV(auto_major,0));
class_destroy(cls);
unregister_chrdev(auto_major,"replay");
而要完成其他函数之前,我们要先申请一块空间来存放写入驱动的数据,所以我们先申请空间:
static char *replay_buf;
入口函数:
replay_buf = kmalloc(1024*1024,GFP_KERNEL);
if(!replay_buf){
printk("can't alloc for mylog_buf\n");
return -EIO;
}
出口函数:
kfree(replay_buf);
写函数:将文件中的数据写入驱动程序的buf中。
static int replay_w = 0; /* 记录数据写入的位置 */
static int replay_r = 0; /* 记录数据读取的位置 */
static ssize_t replay_write (struct file *file, const char __user *buf, size_t size, loff_t *loff_t)
{
int err;
/* 检验是否超出replay_buf的范围,如果超出则提示错误并返回 */
if(replay_w + size > (1024*1024)){
printk(" replay_buf full! \n ");
return -EIO;
}
/* 将文件从用户空间读入,这里使用replay_w记录数据存放的位置 */
err = copy_from_user(replay_buf+replay_w,buf,size); /* 这里涉及copy_from_user的返回值,当完成操作时返回0,没有完成返回非0 */
if(err){
return -EIO;
}else{
replay_w += size;
}
return size;
}
ioctl函数:启动复现功能,同时加入标记功能来记录重要信息的位置。
static int replay_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
char buf[100];
switch (cmd){
case REPLAY_MODE_ON: {
replay_timer.expires = jiffies + 1; /* 这里的超时时间为当前时间加5ms,所以我们一调用add_timer函数就可以进入处理函数了 */
del_timer(&replay_timer); /* 为了可以重复复现,在添加定时器之前要先删除定时器,防止发生错误 */
add_timer(&replay_timer);
break;
}
case REPLAY_MODE_TAG:{
copy_from_user(buf,(const void __user *)arg,100); /* 从用户空间获得标志位 */
buf[99] = '\0';
myprintk("%s\n",buf); /* 记录标志位信息 */
break;
}
}
return 0;
}
这里需要说明的一点是:由于我们在replay_buf中记录的时间是过去的时间,而我们要复现记录,要用到现在的时间。所以我们只能计算在记录中时间的间隔,使用过去的时间间隔加上现在的时间来复现过程。所以这里我们要用到定时器。而复现的过程也是在定时器的处理函数中完成的。所以上面ioctl函数使用了定时器的函数。
例如当记录的时间为100,100,102,1000时,我们的操作为:
1. 把时间为100的数据取出,然后上报
2. 判断是否还有时间为100的数据,如果有继续上报
3,如果没有时间为100 的数据,启动定时器,超时时间为jiffies+(102-100)
4,时间到,上报时间为102的数据,判断是否还有时间为100的数据,如果有继续上报,如果没有时间为100 的数据,启动定时器,超时时间为jiffies+(1000-102)
5,以此循环上报各个时间位的数据
所以我们关于定时器的程序为:
static struct timer_list replay_timer;
void replay_timer_func(unsigned long data)
{
/* 把replay_buf里的一些数据取出来上报
* 读出第1行数据, 确定time值, 上报第1行
* 继续读下1行数据, 如果它的time等于第1行的time, 上报
* 否则: mod_timer
*/
unsigned int time;
unsigned int type;
unsigned int code;
int val;
static unsigned int pre_time = 0;
static unsigned int pre_type = 0;
static unsigned int pre_code = 0;
static int pre_val = 0;
int ret = 0;
printk("replay_timer_func\n");
if (pre_time != 0)
{
/* 上报事件 */
input_event(s3c_ts_dev, pre_type, pre_code, pre_val);
}
while(1){
ret = read_one_line(line);
if(ret == 0){
printk("end of input replay\n");
del_timer(&replay_timer);
pre_time = pre_type = pre_code = 0;
pre_val = 0;
replay_r = replay_w = 0;
break;
}
/* 处理数据 */
time = 0;
type = 0;
code = 0;
val = 0;
printk("sscanf\n");
sscanf(line,"%x %x %x %d",&time,&type,&code,&val);
if (!time && !type && !code && !val)
continue;
else{
if((pre_time == 0) || (pre_time == time)){
/* 上报事件 */
input_event(s3c_ts_dev, type,code, val);
if(pre_time == 0){
pre_time = time;
}
}else{
/* 根据下一个要上报的数据的时间 mod_timer */
mod_timer(&replay_timer,jiffies+(time-pre_time));
pre_time = time;
pre_type = type;
pre_code = code;
pre_val = val;
break;
}
}
}
}
入口函数:
init_timer(&replay_timer);
replay_timer.function = replay_timer_func;
由我们的记录函数的格式为:myprintk("0x%08x 0x%08x 0x%08x %d\n",time,type,code,val);,可知我们的数据是一行一行记录的,所以我们也应该将数据一行一行的读出,而读一行数据的函数为:
static int read_one_line(char *line)
{
int i = 0;
/* 清除前导的空格,回车,换行 */
while(replay_r <= replay_w){
if((replay_buf[replay_r] == '\n') || (replay_buf[replay_r] == ' ') || (replay_buf[replay_r] == '\r') || (replay_buf[replay_r] == '\t')){
replay_r++;
}else{
break;
}
}
/* 读一行内容到line中 */
while(replay_r <= replay_w){
if((replay_buf[replay_r] == '\n') || (replay_buf[replay_r] == '\r'))
break;
else{
line[i] = replay_buf[replay_r];
replay_r++;
i++;
}
}
line[i] = '\0'; /* 在line的末尾加‘\0’表示字符串结尾 */
return i;
}
3、测试的应用程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#define REPLAY_MODE_ON 1
#define REPLAY_MODE_TAG 2
/* Usage:
* ./input_replay write <file>
* ./input_replay replay
* ./input_repaly tag <string>
*/
void print_usage(char *file)
{
printf("Usage:\n");
printf("%s write <file>\n", file);
printf("%s replay\n", file);
printf("%s tag <string>\n", file);
}
int main(int argc,char **argv)
{
int fd;
int fd_data;
int cnt;
int buf[100];
if(argc != 3 && argc != 2){
print_usage(argv[0]);
return -1;
}
fd = open("/dev/replay",O_RDWR);
if(fd < 0){
printf("can't open /dev/replay\n");
return -1;
}
if(strcmp("replay",argv[1]) == 0){
ioctl(fd,REPLAY_MODE_ON);
}
else if(strcmp("write",argv[1]) == 0){
if(argc != 3){
print_usage(argv[0]);
return -1;
}
fd_data = open(argv[2],O_RDONLY);
if(fd_data < 0){
printf("can't open %s\n",argv[2]);
return -1;
}
while(1){
cnt = read(fd_data,buf,100);
if(cnt == 0){
printf("write ok!!!\n");
break;
//printf("user space %s\n",buf);
}else{
write(fd,buf,cnt);
}
}
}
else if(strcmp("tag",argv[1]) == 0){
if(argc != 3){
print_usage(argv[0]);
return -1;
}
ioctl(fd,REPLAY_MODE_TAG,argv[2]);
}
else{
print_usage(argv[0]);
return -1;
}
return 0;
}
对于上面的测试程序我想说的是open,read,write,ioctl函数的用法。
open函数:open(文件名/设备名,操作属性),返回值fd小于0表示不成功。
read函数:read(fd,buf,size),其中fd为open所传递的fd,buf表示将读到的内容存到buf中,而size表示读多少字节数据,返回值 表示读了多少数据。
write函数:write(fd,buf,size),其中fd为open所传递的fd,buf表示将buf中的内容传到内核的write函数中,而size表示传递多少字节数据。
ioctl函数:ioctl(fd,cmd,arg),其中fd为open所传递的fd,cmd表示传到内核的ioctl函数中cmd参数,arg表示传到内核的ioctl函数中arg参数。
5.4、测试
5.4.1、安装tslib
解压在usr/local目录下
1、安装m4
wget http://mirrors.kernel.org/gnu/m4/m4-1.4.13.tar.gz \
&& tar -xzvf m4-1.4.13.tar.gz \
&& cd m4-1.4.13 \
&& ./configure -prefix=/usr/local
sudo make
sudo make install
2、安装autoconf
wget http://mirrors.kernel.org/gnu/autoconf/autoconf-2.65.tar.gz \
&& tar -xzvf autoconf-2.65.tar.gz \
&& cd autoconf-2.65 \
&& ./configure -prefix=/usr/local
sudo make
sudo make install
3、安装automake
wget http://mirrors.kernel.org/gnu/automake/automake-1.11.tar.gz \
&& tar xzvf automake-1.11.tar.gz \
&& cd automake-1.11 \
&& ./configure -prefix=/usr/local
sudo make
sudo make install
4、安装libtool
wget http://mirrors.kernel.org/gnu/libtool/libtool-2.2.6b.tar.gz \
&& tar xzvf libtool-2.2.6b.tar.gz \
&& cd libtool-2.2.6b \
&& ./configure -prefix=/usr/local
sudo make
sudo make install
5.4.2、配置内核
make menuconfig
配置内核时要加上:
Device Drivers->Input device support->Event interface && Event debugging
make uImage
cp arch/arm/boot/uImage /work/nfs_root/uImage_nots
5.4.3、编译、安装tslib
编译:
tar xzf tslib-1.4.tar.gz
cd tslib
./autogen.sh
mkdir tmp
echo “ac_cv_func_malloc_0_nonnull=yes” >arm-linux.cache
./configure --host=arm-linux --cache-file=arm-linux.cache --prefix=$(pwd)/tmp
make
make install
安装:
cd tmp
cp * -rf /nfs_root_first_fs
5.4.4、测试复现的驱动程序
1、装在lcd驱动程序
insmod cfbcopyarea.ko
insmod cfbfillrect.ko
insmod cfbimgblt.ko
insmod lcd.ko
2、装载mymsg.ko
insmod mymsg.ko
cat proc/mymsg
3、装在触摸屏驱动程序
insmod s3c_ts.ko
cat proc/mymsg
4、设置tslib的环境变量
1.
修改 /etc/ts.conf第1行(去掉#号和第一个空格):
#module_raw input
改为:
module_raw input
2.
export TSLIB_TSDEVICE=/dev/event0
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/lib/ts
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
5、添加tag
./input_replay tag zdy_ts_test
cat proc/mymsg
6、运行ts_test写字
ts_test
7、保存mymsg
cp /proc/mymsg /ts_zdy.txt
8、在ubuntu下设置权限
cd /work/nfs_root/first_fs
sudo chmod 777 ts_zdy.txt
9、修改ts_zdy.txt
删除tag和之前的无用数据
vi ts_zdy.txt
10、在开发板下添加ts_zdy.txt到复现缓冲区
./input_replay write /ts_zdy.txt
11、后台运行ts_test
ts_test &
测试完之后
ps :查看ts_test的后台进程号
kill -9 <ts_test的进程号>
12、执行复现操作
./input_replay replay