In this tutorial I will explain how to debug the encoder application to discover how it is controlling camera motor.
Inside camera (serial terminal) execute:
# ./gdbserver 192.168.1.1:3333 /mnt/system/bin/encoder
Process /mnt/system/bin/encoder created; pid = 326
Listening on port 3333
On your workstation:
$ arm-linux-gdb encoder
(gdb) target remote 192.168.1.126:3333
Listing all defined functions:
(gdb) info fun
Non-debugging symbols:
0x0000a930 _init
0x0000a958 fileno
0x0000a964 getpagesize
0x0000a970 fputs
0x0000a97c abort
0x0000a988 __errno_location
0x0000a994 sigemptyset
0x0000a9a0 sprintf
0x0000a9ac popen
0x0000a9b8 open
0x0000a9c4 connect
0x0000a9d0 mmap
0x0000a9dc snd_pcm_hw_params_any
0x0000a9e8 statfs64
0x0000a9f4 getpid
0x0000aa00 snd_output_close
0x0000aa0c strerror
0x0000aa18 snd_pcm_close
0x0000aa24 getsockname
0x0000aa30 memcmp
0x0000aa3c inet_ntoa
0x0000aa48 shutdown
0x0000aa54 snd_pcm_status_get_state
0x0000aa60 msgget
0x0000aa6c signal
0x0000aa84 realloc
0x0000aa90 snd_pcm_hw_params_set_period_time_near
0x0000aa9c snd_pcm_sw_params_get_xfer_align
0x0000aaa8 __xstat64
0x0000aab4 localtime
0x0000aac0 snd_pcm_prepare
0x0000aacc snd_pcm_sw_params_sizeof
0x0000aad8 getgrnam
0x0000aae4 strchr
0x0000aaf0 vsnprintf
0x0000aafc recv
0x0000ab08 getenv
0x0000ab14 sem_wait
0x0000ab20 inet_addr
0x0000ab2c system
0x0000ab38 strncpy
0x0000ab44 putchar
0x0000ab50 write
0x0000ab5c sendto
0x0000ab68 listen
0x0000ab74 lseek64
0x0000ab80 fgets
0x0000ab8c memset
0x0000ab98 setitimer
0x0000aba4 fopen64
0x0000abb0 snd_pcm_status_get_trigger_tstamp
0x0000abbc __strtol_internal
0x0000abc8 snd_pcm_hw_params_get_period_size
0x0000abd4 __libc_start_main
0x0000abe0 execl
0x0000abec _IO_getc
0x0000abf8 _exit
0x0000ac04 snd_pcm_hw_params
0x0000ac10 snd_pcm_sw_params_set_avail_min
0x0000ac1c strrchr
0x0000ac28 tcgetattr
0x0000ac34 snd_pcm_hw_params_sizeof
0x0000ac40 set_gpio_input
0x0000ac4c piulib_init
0x0000ac58 read
0x0000ac64 msgsnd
0x0000ac70 perror
0x0000ac7c usleep
0x0000ac88 snd_pcm_open
0x0000ac94 gettimeofday
0x0000aca0 fdopen
0x0000acac __ctype_toupper_loc
0x0000acb8 free
0x0000acc4 siglongjmp
0x0000acd0 __lxstat64
0x0000acdc snd_pcm_sw_params_set_xfer_align
0x0000ace8 snd_pcm_sw_params_set_sleep_min
0x0000acf4 access
0x0000ad00 sigaction
0x0000ad0c fflush
0x0000ad18 snd_pcm_sw_params
0x0000ad24 piulib_exit
0x0000ad30 opendir
0x0000ad3c accept
0x0000ad48 tcflush
0x0000ad54 ioctl
0x0000ad60 socket
0x0000ad6c dup2
0x0000ad78 __ctype_b_loc
0x0000ad84 fseek
0x0000ad90 inet_aton
0x0000ad9c pthread_mutex_unlock
0x0000ada8 snd_pcm_status_sizeof
0x0000adb4 isatty
0x0000adc0 umask
0x0000adcc fclose
0x0000add8 setuid
0x0000ade4 snd_pcm_writei
0x0000adf0 mktime
0x0000adfc readdir64
0x0000ae08 memcpy
0x0000ae14 cfsetospeed
0x0000ae20 strlen
0x0000ae2c snd_pcm_wait
0x0000ae38 fopen
0x0000ae44 snd_pcm_readi
0x0000ae50 snd_pcm_resume
0x0000ae5c snd_strerror
0x0000ae68 unlink
0x0000ae74 snd_pcm_sw_params_set_stop_threshold
0x0000ae80 getpwuid
0x0000ae8c getppid
0x0000ae98 waitpid
0x0000aea4 feof
0x0000aeb0 strcpy
0x0000aebc longjmp
0x0000aec8 printf
0x0000aed4 ftok
0x0000aee0 chdir
0x0000aeec ctime
0x0000aef8 bind
0x0000af04 getuid
0x0000af10 snd_config_update_free_global
0x0000af1c atoi
0x0000af28 mkstemp64
0x0000af34 select
0x0000af40 closedir
0x0000af4c close
0x0000af58 fwrite
0x0000af64 snd_pcm_format_physical_width
0x0000af70 snd_pcm_hw_params_get_buffer_time_max
0x0000af7c fprintf
0x0000af88 strstr
0x0000af94 time
0x0000afa0 setvbuf
0x0000afac execve
0x0000afb8 malloc
0x0000afc4 snd_pcm_state_name
0x0000afd0 snd_pcm_hw_params_get_buffer_size
0x0000afdc pthread_mutex_lock
0x0000afe8 sigprocmask
0x0000aff4 snd_pcm_info_sizeof
0x0000b000 gethostname
0x0000b00c snd_pcm_hw_params_set_rate_near
0x0000b018 pthread_create
0x0000b024 snd_pcm_hw_params_set_access
0x0000b030 sleep
0x0000b03c sigaddset
0x0000b048 msgrcv
0x0000b054 strncasecmp
0x0000b060 memmove
0x0000b06c strcat
0x0000b078 send
0x0000b084 snd_pcm_hw_params_set_channels
0x0000b090 getcwd
0x0000b09c sem_post
0x0000b0a8 snd_pcm_format_set_silence
0x0000b0b4 settimeofday
0x0000b0c0 puts
0x0000b0cc fork
0x0000b0d8 __fxstat64
0x0000b0e4 setsockopt
0x0000b0f0 tcsetattr
0x0000b0fc fcntl
0x0000b108 snd_pcm_hw_params_set_buffer_time_near
0x0000b114 sscanf
0x0000b120 gmtime
0x0000b12c memchr
0x0000b138 strncmp
0x0000b144 open64
0x0000b150 snd_pcm_info
0x0000b15c munmap
0x0000b168 pipe
0x0000b174 fread
0x0000b180 getsockopt
0x0000b18c setgid
0x0000b198 snprintf
0x0000b1a4 mmap64
0x0000b1b0 init_gpio_lib
0x0000b1bc snd_output_stdio_attach
0x0000b1c8 gethostbyname
0x0000b1d4 recvfrom
0x0000b1e0 ferror
0x0000b1ec getpwnam
0x0000b1f8 strcmp
0x0000b204 __sigsetjmp
0x0000b210 herror
0x0000b21c piu_register
0x0000b228 __strdup
0x0000b234 exit
0x0000b240 cfsetispeed
0x0000b24c pclose
0x0000b258 sem_init
0x0000b264 set_gpio_output
0x0000b270 snd_pcm_hw_params_set_format
0x0000b27c getgid
0x0000b288 snd_pcm_sw_params_current
0x0000b294 get_gpio_value
0x0000b2a0 snd_pcm_status
0x0000b2ac piu_tx
0x0000b2b8 snd_pcm_sw_params_set_start_threshold
0x0000b2c4 geteuid
0x00049070 _fini
0x40008b20 _dl_rtld_di_serinfo
0x0000b294 get_gpio_value
0x0000b2a0 snd_pcm_status
0x0000b2ac piu_tx
0x0000b2b8 snd_pcm_sw_params_set_start_threshold
0x0000b2c4 geteuid
0x00049070 _fini
0x40008b20 _dl_rtld_di_serinfo
0x4000f660 _dl_debug_state
0x40010f60 _dl_mcount
0x40011320 __tls_get_addr
0x40011670 _dl_tls_setup
0x40011740 _dl_get_tls_static_info
0x40011850 _dl_allocate_tls_init
0x40011a90 _dl_allocate_tls
0x40011ac0 _dl_deallocate_tls
0x40011e60 ___tls_get_addr
0x400122a0 _dl_make_stack_executable
0x400157b0 __libc_memalign
0x400158c0 malloc
0x400158f0 calloc
0x40015950 free
0x40015a30 realloc
(gdb)
We are insterested to know which files were opened and what was write to it.
Then we will put breackpoints at open, open64, fopen and write:
(gdb) b open
Breakpoint 1 at 0xa9b8
(gdb) b fopen
Breakpoint 2 at 0xae38
(gdb) b write
Breakpoint 3 at 0xab50
(gdb) b open64
Breakpoint 6 at 0xb144
(gdb) b popen
Breakpoint 7 at 0xa9ac
(gdb) c
Continuing.
Now let us to discover what is happening.
(gdb) target remote run
A program is being debugged already. Kill it? (y or n) n
Program not killed.
(gdb) c
Continuing.
Breakpoint 3, 0x0000ab50 in write ()
(gdb)
Let’s find out which file descriptor and what is written on it:
(gdb) p /x $r0
$1 = 0x4
Hmm, file descriptor 4, let us to know what is it…
$ telnet 192.168.1.126
Trying 192.168.1.126...
Connected to 192.168.1.126.
Escape character is '^]'.
(none) login: root
warning: cannot change to home directory
/ # ps ax
...
323 root 1728 S ./gdbserver 192.168.1.126:3333 /mnt/system/bin/encode
326 root 3868 T /mnt/system/bin/encoder
327 root 2840 S -sh
351 root 2840 R ps ax
/ #
Ok, Process ID of encoder is 326, then let see which files are opened for this process:
/ # ls -l /proc/326/fd/
lrwx------ 1 root root 64 Jan 1 01:13 0 -> /dev/ttyS0
lrwx------ 1 root root 64 Jan 1 01:13 1 -> /dev/ttyS0
lrwx------ 1 root root 64 Jan 1 01:13 2 -> /dev/ttyS0
lrwx------ 1 root root 64 Jan 1 01:13 3 -> socket:[148]
lrwx------ 1 root root 64 Jan 1 01:13 4 -> /dev/i2c-0
Wow, it is /dev/i2c-0
Now let us to return to gdb interface to know what will be write to it:
Breakpoint 3, 0x0000ab50 in write ()
(gdb) p /x $r0
$1 = 0x4
(gdb) x /1x $r1
0xbed7d9d7: 0x0000000a
(gdb) p /x $r2
$2 = 0x1
Hmm, it will write only 1 byte (r2 = 1) and this byte it 0x0A (pointer at r1).
Ok, move on…
Another write operation:
Breakpoint 3, 0x0000ab50 in write ()
(gdb) p /x $r0
$3 = 0x4
(gdb) x /1x $r1
0xbed7d9d7: 0x0000000b
(gdb) p /x $r2
$4 = 0x1
Hey, it should be reading something from i2c as well, let us to put a breakpoint at read function:
(gdb) b read
Breakpoint 4 at 0xac58
(gdb) c
Continuing.
Breakpoint 4, 0x0000ac58 in read ()
Let gather more information:
(gdb) info frame
Stack level 0, frame at 0xbed7d9d4:
pc = 0xac58 in read; saved pc 0x28d5c
called by frame at 0xbed7d9d4
Arglist at 0xbed7d9d4, args:
Locals at 0xbed7d9d4, Previous frame's sp is 0xbed7d9d4
This function was called from 0x28d5c (saved pc).
We could obtain this information from link register (lr) as well, lr is an alias to r14:
(gdb) p /x $r14
$5 = 0x28d5c
Good, let put a breakpoint at 0x28d5c
(gdb) b *0x28d5c
Breakpoint 5 at 0x28d5c
Now, let run the read function and wait it return to 0x28d5c
Breakpoint 5, 0x00028d5c in ?? ()
(gdb) p /x $r0
$6 = 0x1
(gdb) x /1x $r1
0xbed7d9d6: 0x21
Ok, it read 1 byte as expected and this byte is 0x21.
Keep going:
(gdb) c
Continuing.
Breakpoint 3, 0x0000ab50 in write ()
A new write:
(gdb) p /x $r0
$7 = 0x4
(gdb) x /1x $r1
0xbed7d9e0: 0x12
(gdb) p /x $r2
$8 = 0x2
It is writing at same file descriptor (4 = i2c-0), but now it is write 2 bytes, first one is 0x12, but let us see both:
(gdb) x /1x 0xbed7d9e0
0xbed7d9e0: 0x12
(gdb) x /1x 0xbed7d9e1
0xbed7d9e1: 0x80
Ok, let’s to continue:
(gdb) c
Continuing.
Breakpoint 3, 0x0000ab50 in write ()
(gdb) p /x $r0
$9 = 0x4
(gdb) x /1x $r1
0xbed7d9e0: 0x12
(gdb) p /x $r2
$10 = 0x2
(gdb) x /1x 0xbed7d9e1
0xbed7d9e1: 0x00
And another write, but now the second value is 0x00 instead of 0x80.
In this mean time I noticed these text printed at minicom (where gdbserver is running) :
ov7725 id1 id2:77-21
this is ov7725
reglen 97
So, this writing/reading is to initialize ov7725 camera, nice to know, but I’m not interested on camera sensor, not yet.
Then let to remove breakpoints at write and read, and keep only with open and fopen:
Delete write breakpoint:
(gdb) delete 3
Delete read breakpoint:
(gdb) delete 4
Delete breakpoint of read function return:
(gdb) delete 5
Program received signal SIG32, Real-time event 32.
0x4002712c in ?? ()
(gdb) c
Then it will open in the sequence:
/dev/piu
/dev/mem
/dev/vip-note
/dev/dv
/dev/vip
avc_enc.dlm
And now this is a very suspect file:
Breakpoint 6, 0x0000b144 in open64 ()
(gdb) x /1s $r0
0x4ad40: "/dev/ttyS1"
Let us to put a breakpoint at return of this function, then we will know the file descriptor number of this file:
(gdb) p /x $r14
$12 = 0x12ac4
(gdb) b *0x12ac4
Breakpoint 9 at 0x12ac4
(gdb) c
Continuing.
You will see many this signal events, before open64 function to return:
Program received signal SIG32, Real-time event 32.
[Switching to Thread 369]
0x4002712c in ?? ()
(gdb) c
Continuing.
[Switching to Thread 326]
If you get bored then you could remove the breakpoint at open64, it will speed up this process:
Breakpoint 6, 0x0000b144 in open64 ()
(gdb) delete 6
(gdb) c
Continuing.
And finally:
Breakpoint 9, 0x00012ac4 in ?? ()
(gdb)
Now we can see the file descriptor number:
(gdb) p /x $r0
$13 = 0xc
In fact I can see it at proc:
/ # ls -l /proc/326/fd/
...
lrwx------ 1 root root 64 Jan 1 01:47 12 -> /dev/ttyS1
This is file descriptor 12 (0xC).
Please continue with debugging until point where camera start to do self calibration.
Now we can return with breackpoint at write function:
(gdb) b write
Breakpoint 10 at 0xab50
(gdb) c
Now switch to telnet terminal and execute the CGI script to move camera up:
/tmp # export QUERY_STRING=command=1\&user=admin\&pwd=
/tmp # ./decoder_control.cgi
Content-Type:text/plain
Cache-Control:no-cache
/tmp #
Switch back to gdb terminal and continue with execution:
(gdb) c
Continuing.
[New Thread 355]
[Switching to Thread 355]
Breakpoint 10, 0x0000ab50 in write ()
The encoder is now trying to write something in the camera:
(gdb) p /x $r0
$16 = 0xc
(gdb) x /1s $r1
0xbe5fe044: "\34201\n\377"
(gdb) p /x $r2
$17 = 0x8
It is expecting to write 8 bytes, let see what are these bytes:
(gdb) x /1x 0xbe5fe044
0xbe5fe044: 0xe2
(gdb) x /1x 0xbe5fe045
0xbe5fe045: 0x01
(gdb) x /1x 0xbe5fe046
0xbe5fe046: 0x0a
(gdb) x /1x 0xbe5fe047
0xbe5fe047: 0xff
(gdb) x /1x 0xbe5fe048
0xbe5fe048: 0x00
(gdb) x /1x 0xbe5fe049
0xbe5fe049: 0x00
(gdb) x /1x 0xbe5fe04a
0xbe5fe04a: 0x00
(gdb) x /1x 0xbe5fe04b
0xbe5fe04b: 0x23
(gdb)
Great, we could try to send these sequence directly from telnet terminal:
/tmp # printf "\xE2\x01\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Worked fine!!! Now these are other commands:
Move Up:
# printf "\xE2\x01\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Move Down:
# printf "\xE2\x02\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Move Right:
# printf "\xE2\x03\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Move Left
# printf "\xE2\x06\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Move Right-Up
# printf "\xE2\x04\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Move Left-Down:
# printf "\xE2\x08\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Move Right-Down:
# printf "\xE2\x05\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Move Left-Up:
# printf "\xE2\x07\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1
Other commands are also supported as horizontal/vertical patrol.
Update: Other interesting start point to you know more about GDB:
http://brundlelab.wordpress.com/2010/06/21/playing-with-gdb-reverse-engineer-your-way/