2007年12月9日日曜日

Shared library "Hello World!" for Android

This article describes the trace I followed when I managed to compile and run the shared library type of "Hello, world!" for Android. I confirmed this procedure only on my host (Fedora 8). Sorry for Windows and Mac users.


I would like to appreciate developers who posted useful messages on Android Developers and Android Internal news groups. Especially I would like to thank Benno who provided busybox and strace binary which work on Android.


I would like to suggest you to read the "Native C applications for Android" written by Benno and my previous article before reading this one.


The following description assumes that you have toolchains, strace, busybox and system image of Android in the hand. Otherwise, read my previous article at first.


I hope this article is useful for all Android developers.




1. Prepare source files.


hello.h:
extern void hello(const char* name);

hello.c:
#include <stdio.h>

void hello(const char* name)
{
printf("Hello %s!\n", name);
}

start.c:
#include <stdlib.h>

extern int main(int argc, char **argv);

void _start(int argc, char **argv)
{
exit (main (argc, argv));
}

main.c:
#include "hello.h"

int main(void)
{
hello("World!");
return 0;
}

2. Make shared library.


$ arm-none-linux-gnueabi-gcc -fpic -c hello.c
$ arm-none-linux-gnueabi-ld -shared -o libhello.so hello.o

3. Make dynamically linked executable.


 $ arm-none-linux-gnueabi-gcc -c start.c
$ arm-none-linux-gnueabi-gcc -c main.c
$ arm-none-linux-gnueabi-ld \
--dynamic-linker /system/bin/linker -nostdlib \
-rpath /system/lib -rpath ~/tmp/android/system/lib \
-L . -L ~/tmp/android/system/lib -lc -lhello -o hello2 start.o main.o

4. Copy the shared library and the executable to Android. The following description assumes that there is /data/hello directory on Android.
The '#' is the prompt of Android shell.


 $ adb push libhello.so /data/hello
$ adb push hello2 /data/hello
# ./hello2
WARNING: `libhello.so` is not a prelinked library
[2] Segmentation fault ./hello2

5. What happened? Anyway let's take a look at the output of strace.


    11 open("libc.so", O_RDONLY|O_LARGEFILE)   = -1 ENOENT (No such file or directory)
12 open("/system/lib/libc.so", O_RDONLY|O_LARGEFILE) = 3
13 lseek(3, -8, SEEK_END) = 231908
14 read(3, "\0\0\340\257PRE ", 8) = 8
15 mmap2(0xafe00000, 233472, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xafe00000
16 close(3) = 0
17 mmap2(0xafe39000, 45056, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0xafe39000
18 mprotect(0xafe00000, 221184, PROT_READ|PROT_EXEC) = 0
19 open("libhello.so", O_RDONLY|O_LARGEFILE) = 3
20 lseek(3, -8, SEEK_END) = 2344
21 read(3, "a_start\0", 8) = 8
22 fstat64(1, {st_mode=S_IFREG|0666, st_size=1432, ...}) = 0
23 brk(0) = 0x11000
24 brk(0x11000) = 0x11000
25 brk(0x13000) = 0x13000
26 write(1, "WARNING: `libhello.so` is not a "..., 50WARNING: `libhello.so` is not a prelinked library
27 ) = 50
28 lseek(3, 0, SEEK_END) = 2352
29 mmap2(0x80100000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x80100000
30 close(3) = 0
31 --- SIGSEGV (Segmentation fault) @ 0 (801082bc) ---

At the line 12-14, linker opens libc.so and reads the 8 bytes at the end of the file. It is "\0\0\340\257PRE ". It is interesting that this linker checks the fixed pattern at the end of the library. Then at the line 15, linker mmaps the library and at line 17, drops writable flag. On the other hand, at line 19, linker opens libhello.so. At line 20 and 21, linker checks 8 bytes at the end of the file. It is "a_start\0" and the linker warns "`libhello.so` is not a prelinked library". What is prelinked library? How to make it? Some developers have argued this issue. A developer suggested it is just a warning. So I guessed there may be another problem which causes SIGSEGV rather than "not a prelinked library" issue.


By the way, I found a bug in the source of strace 4.5.15 on which Benno's strace is based. The bug hides the address of segmentation fault. The patch and patched strace binary is available.


Now, you see the segmentaion fault occurs at 0x801082bc at the line 31 on the list above. See the line 29. The fault address is beyond the mmaped area. Let's see the program header of libhello.so by readelf. The fault address is the top of dynamic section.


 Elf file type is DYN (Shared object file)
Entry point 0x274
There are 4 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00000000 0x00000000 0x002bc 0x002bc R E 0x8000
LOAD 0x0002bc 0x000082bc 0x000082bc 0x00088 0x00088 RW 0x8000
DYNAMIC 0x0002bc 0x000082bc 0x000082bc 0x00078 0x00078 RW 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4

Section to Segment mapping:
Segment Sections...
00 .hash .dynsym .dynstr .rel.plt .plt .text .rodata
01 .dynamic .got
02 .dynamic
03

Uh... The linker does not mmap the second segment. How about standard shared libraries in Android, for example, libc.so?


 Elf file type is DYN (Shared object file)
Entry point 0x92d0
There are 3 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
EXIDX 0x035df8 0x00035df8 0x00035df8 0x00110 0x00110 R 0x4
LOAD 0x000000 0x00000000 0x00000000 0x382a0 0x43978 RWE 0x8000
DYNAMIC 0x036de8 0x00036de8 0x00036de8 0x000a8 0x000a8 RW 0x4

Section to Segment mapping:
Segment Sections...
00 .ARM.exidx
01 .hash .dynsym .dynstr .rel.dyn .rel.plt .plt .text .rodata
.ARM.exidx .data.rel.ro .dynamic .got .data .bss
02 .dynamic

Look, there is only one segment with LOAD type. In another word, the text and data sections are not page aligned and the combined segment has read/write/exec flag.


Now, the default linker script need to be modified. The default linker script is available at $toolchains_home/arm-none-linux-gnueabi/lib/ldscripts/armelf_linux_eabi.xsc. Copy it to the current directory. Comment out three lines and add one line replacing the first commented out line.


/* . = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); */
. = .; /* this line replaces the line above */

/* . = DATA_SEGMENT_RELRO_END (0, .); */

/* . = DATA_SEGMENT_END (.); */


The patch is available, either.


6. Rebuild shared library with -T option.


 $ arm-none-linux-gnueabi-ld \
-T armelf_linux_eabi.xsc \
-shared -o libhello.so hello.o

Check new library and confirm the data and text sections are in the same segment with read/write/exec flag.


 Elf file type is DYN (Shared object file)
Entry point 0x254
There are 3 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x00000000 0x00000000 0x00324 0x00324 RWE 0x8000
DYNAMIC 0x00029c 0x0000029c 0x0000029c 0x00078 0x00078 RW 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4

Section to Segment mapping:
Segment Sections...
00 .hash .dynsym .dynstr .rel.plt .plt .text .rodata .dynamic .got
01 .dynamic
02

7. Rebuild executable, send library and executable to Android and run it.


 $ arm-none-linux-gnueabi-ld \
--dynamic-linker /system/bin/linker -nostdlib \
-rpath /system/lib -rpath ~/tmp/android/system/lib \
-L . -L ~/tmp/android/system/lib -lc -lhello -o hello2 start.o main.o
$ adb push libhello.so /data/hello
$ adb push hello2 /data/hello
# ./hello2
WARNING: `libhello.so` is not a prelinked library
Hello World!!

There is still the warning but the program prints "Hello World!".



8. Check the output of strace.


 # strace ./hello2
11 open("libc.so", O_RDONLY|O_LARGEFILE) = -1 ENOENT (No such file or directory)
12 open("/system/lib/libc.so", O_RDONLY|O_LARGEFILE) = 3
13 lseek(3, -8, SEEK_END) = 231908
14 read(3, "\0\0\340\257PRE ", 8) = 8
15 mmap2(0xafe00000, 233472, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xafe00000
16 close(3) = 0
17 mmap2(0xafe39000, 45056, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0) = 0xafe39000
18 mprotect(0xafe00000, 221184, PROT_READ|PROT_EXEC) = 0
19 open("libhello.so", O_RDONLY|O_LARGEFILE) = 3
20 lseek(3, -8, SEEK_END) = 2312
21 read(3, "a_start\0", 8) = 8
22 fstat64(1, {st_mode=S_IFREG|0666, st_size=1432, ...}) = 0
23 brk(0) = 0x11000
24 brk(0x11000) = 0x11000
25 brk(0x13000) = 0x13000
26 write(1, "WARNING: `libhello.so` is not a "..., 50WARNING: `libhello.so` is not a prelinked library
27 ) = 50
28 lseek(3, 0, SEEK_END) = 2320
29 mmap2(0x80100000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0x80100000
30 close(3) = 0
31 mprotect(0, 0, PROT_READ|PROT_EXEC) = 0
32 fstat64(1, {st_mode=S_IFREG|0666, st_size=1970, ...}) = 0
33 brk(0) = 0x13000
34 brk(0x13000) = 0x13000
35 brk(0x15000) = 0x15000
36 exit_group(0) = ?
37 Process 6028 detached

You see the program ends in success at the line 36.


9. To see memory map, modify main.c.


 #include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
hello("World!");
for(;;) sleep(1); // Zzz...
return 0;
}

This program sleeps until killed.


 # ./hello2 &
# WARNING: `libhello.so` is not a prelinked library
Hello World!!

# ps | grep hello2
root 5957 450 484 228 c004bc44 afe08b9c S ./hello2
# cat /proc/5957/maps
00008000-00009000 r-xp 00000000 1f:01 512 /data/hello/hello2
00010000-00011000 rw-p 00000000 1f:01 512 /data/hello/hello2
00011000-00015000 rwxp 00011000 00:00 0 [heap]
80100000-80101000 rwxp 00000000 1f:01 728 /data/hello/libhello.so
afe00000-afe36000 r-xp 00000000 1f:00 365 /system/lib/libc.so
afe36000-afe39000 rwxp 00036000 1f:00 365 /system/lib/libc.so
afe39000-afe44000 rw-p afe39000 00:00 0
b0000000-b0014000 rwxp 00000000 1f:00 272 /system/bin/linker
b0014000-b0019000 rwxp b0014000 00:00 0
be7f7000-be80c000 rw-p befeb000 00:00 0 [stack]

# kill 5957

The interesting point is that the libc.so is mapped to two areas, one is
read/exec and another is read/write/exec. On the other hand, libhello.so is
mapped to one area with read/write/exec flag. Further research is needed.

3 件のコメント:

Adrian Taylor さんのコメント...

Tsubarashii!

匿名 さんのコメント...

Thanks for the great article.

When I try executing this, I am getting the following error

arm-none-linux-gnueabi-ld: cannot find -lc

and am unable to create executable binary. Do you have any idea in fixing this?

Thanks in advance,
freelancer.kiran@yahoo.com

jbirkler さんのコメント...

not looking at the kernel lately, but one reason for mmaping a lib twice would be to save RAM memory. mmap as much as possible read-only, then that underlaying RAM memory can be shared between processes.
The libhello is mmaped with write persmissions; it cannot be shared between processes.
I think the idea with the "prelinked" is to export that info from the linker; which sections can be mmaped read-only, and which sections needs to be mmaped with write permissions.