2008年1月23日水曜日

Debug a Native Application for Android

Bill Cox wrote the procedure how to debug his statically linked application by gdb. In this article, I show the same procedure can be applied to dynamically linked executable and shared library. At last I have some consideration to the warnings which gdb shows.


Notice it is impossible to debug multi-threaded application for Android with this gdbserver. It is because the gdbserver requires NPTL on linux target to debug multi-threaded application. Porting gdbserver to Android may be possible with source code of the libc. Unfortunately I have no idea how to debug multi-threaded application for Android right now.


I assume that you know how to make dynamically linked executable and shared library for Android. If not, please read first my previous articles here and here.


In the following procedure, $ denotes the prompt of host and # denotes the prompt of emulator.




1. Download gdbserver from here and send it to the emulator.


$ adb push gdbserver /data/tmp

2. Prepare Android system image and hello world programs.

I placed three directories such like the following.
$ ls tmp/android.hello
system
hello1
hello2

The system directory is the android /system directory which is copied from the emulator. At least, there must be bin/linker and lib/libc.so under this directory in order to compile and debug hello world programs. If you do not know how to get the system image of android, refer to the previous articles. The hello1 directory contains the sources of dynamically linked type of program and the hello2 directory contains the sources of shared library type of program.

3. Compile programs with -g option.


$ cd hello1
$ arm-none-linux-gnueabi-gcc -c -fno-builtin-printf -g -o start.o start.c
$ arm-none-linux-gnueabi-gcc -c -fno-builtin-printf -g -o hello.o hello.c
$ arm-none-linux-gnueabi-ld --dynamic-linker /system/bin/linker -nostdlib\
-rpath /system/lib -rpath ~/tmp/android.hello/system/lib\
-L~/tmp/android.hello/system/lib\
-lc -o hello1 start.o hello.o

$ cd hello2
$ arm-none-linux-gnueabi-gcc -c -g -o start.o start.c
$ arm-none-linux-gnueabi-gcc -c -g -o main.o main.c
$ arm-none-linux-gnueabi-gcc -c -fpic -g -fno-builtin-printf\
-o hello.o hello.c
$ arm-none-linux-gnueabi-ld -shared -T armelf_linux_eabi.xsc\
-o libhello.so hello.o
$ arm-none-linux-gnueabi-ld --dynamic-linker /system/bin/linker -nostdlib\
-rpath /system/lib -rpath ~/tmp/android.hello/system/lib\
-L . -L~/tmp/android.hello/system/lib\
-lc -lhello -o hello2 start.o main.o libhello.so

4. Send executables and shared libraries to the emulator.


$ adb hello1 /data/tmp
$ adb hello2 /data/tmp
$ adb libhello.so /data/tmp

You may strip the debug info from the binaries which are placed on the emulator. Gdb will get the debug info from the binaries on the host.

5. Connect to the emulator's console by telnet and redirect the debug port as you like.


$ telnet localhost 5554
redir add tcp:1234:1234
exit

6 Run gdbserver on the emulator.


# cd /data/tmp
# ./gdbserver 10.0.2.2:1234 hello1

The same procedure can be applied to hello2. Note that 10.0.2.2 is the ip address of the host from the view point of emulator.

You can attach to the running program such like the following.
# ./gdbserver 10.0.2.2:1234 <pid>

See 11. below, for the example.

7. Run gdb on the host and connect to the target.


$cd hello1
$arm-none-linux-gnueabi-gdb hello1
(gdb) set sysroot ../
(gdb) set solib-search-path ../system/lib
(gdb) target remote localhost:1234

'set sysroot' set the root directory of the taget image. 'set ../system/lib' set the search path of shared libraries. See 'info gdb' for the detail. The same procedure can be applied to hello2.

8. Start debug as you need.


For example, set breakpoints such like in the following.
In hello1 case,
(gdb) b main
In hello2 case,
(gdb) b hello

9. Result will be such like the following.


In hello1 case,
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0xb0000100 in ?? () from ../system/bin/linker
(gdb) b main
Breakpoint 1 at 0x8344: file hello.c, line 5.
(gdb) c
Continuing.
warning: .dynamic section for "/home/motz/tmp/android.hello/system/lib/libc.so" is not at
the expected address (wrong library or version mismatch?)

Breakpoint 1, main (argc=33540, argv=0x0) at hello.c:5
5 printf("Hello, world!\n");
(gdb) c
Continuing.

Program exited normally.

In hello2 case,
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
warning: Unable to find dynamic linker breakpoint function.
GDB will be unable to debug shared library initializers
and track explicitly loaded dynamic code.
0xb0000100 in ?? () from ../system/bin/linker
(gdb) b hello
Breakpoint 1 at 0x82f8
(gdb) c
Continuing.
warning: .dynamic section for "/home/motz/tmp/android.hello/system/lib/libc.so" is
not at the expected address (wrong library or version mismatch?)
warning: .dynamic section for "libhello.so" is not at the
expected address (wrong library or version mismatch?)

Breakpoint 1, hello (name=0x835c "World!") at hello.c:5
5 printf("Hello %s!\n", name);
(gdb) b printf
Breakpoint 2 at 0xafe0f754
(gdb) c
Continuing.

Breakpoint 2, 0xafe0f754 in printf ()
from /home/kurotsu/tmp/android.hello/system/lib/libc.so
(gdb) c
Continuing.

Program exited normally.

10. Several points to note.


1) "warning: Unable to find dynamic linker breakpoint function. GDB will be unable to debug shared library initializers and track explicitly loaded dynamic code."


This means that the android linker doesn't provide the breakpoint function which gdb assumes to exist. Therefore it is impossible for gdb to set a breakpoint in the linker. Since the android linker is proprietary, gdb doesn't know the internal.

2) "warning: .dynamic section for "xxx/libc.so" is not at the expected address ..."


The cause of this warning is that libc.so is prelinked. The prelink used againt libc.so is proprietry. It breaks the gdb's assumption of relocation.

3) "warning: .dynamic section for "libhello.so" is not at the expected address ..."


The cause of this warning is that the page align between .text and .data of this library is turned off. It is a fake to deceive the android linker. See the previous artical for the detail.

11. Try to debug system executables/libraries.


Note the /system/bin/runtime is multi-threaded, it is impossible to debug with this gdbserver. Please see below as the example how to attach the running process.
# ps | grep runtime
root 455 1 18528 2068 ffffffff afe0861c S /system/bin/runtime
# gdbserver 10.0.2.2:1234 --attach 455
$ cd system
$ telnet localhost:1234 bin/runtime
$ arm-none-linux-gnueabi-gdb bin/runtime
(gdb) set sysroot ../
(gdb) set solib-search-path ./lib
(gdb) target remote localhost:1234
...
bunch of warnings here
...
(gdb) bt
#0 0xafe0861c in __ioctl ()
from /home/motz/tmp/android.hello/system/lib/libc.so
#1 0xafe208bc in ioctl ()
from /home/motz/tmp/android.hello/system/lib/libc.so
#2 0xa9d2b0d6 in android::IPCThreadState::talkWithDriver ()
from /home/motz/tmp/android.hello/system/lib/libutils.so
#3 0x000151c8 in ?? ()
(gdb) signal SIGKILL
Continuing with signal SIGKILL.

Program terminated with signal SIGKILL, Killed.
The program no longer exists.

That's it.