Greetings,
In my journey to learn ARM assembly using Android (Linux) systems, I'm currently playing with Thumb mode.
I'm currently testing if it is possible to write (ELF) library procedures entirely in Thumb. ATM, such tests only led me to Segmentation faults or Illegal Instruction errors.
So I'm wondering if it is possible to write an exported library procedure entirely in Thumb mode.
So, here's a contrived example of a working program listing, named SeriousRating.s, using an internal Thumb mode procedure call :
.data ratings: .ascii "Booh\n\0Yay!\n" .text .syntax unified .globl _start _start: mov r0, #0 // #1 if we got payed by the rated establishment ! blx seriousrating mov r0, #0 mov r7, #1 svc #0 // Print Yay if the argument is positive // Else print booh .thumb seriousrating: push {lr} ldr r1, =ratings cbz r0, .Lprint adds r1, #6 .Lprint: movs r0, #1 // fd : stdout movs r2, #5 // size : The two strings have the same size movs r7, #4 // Linux : sys_write svc #0 pop {pc}
If I compile this example like this :
$ armv7a-hardfloat-linux-gnueabi-as -o SeriousRating.o SeriousRating.s$ armv7a-hardfloat-linux-gnueabi-ld.gold -o SeriousRating SeriousRating.o
$ armv7a-hardfloat-linux-gnueabi-as -o SeriousRating.o SeriousRating.s
$ armv7a-hardfloat-linux-gnueabi-ld.gold -o SeriousRating SeriousRating.o
And run it on my phone like this :
$ adb push SeriousRating /data/local/tmp && adb shell /data/local/tmp/SeriousRating
It works :
Booh
And objdump clearly reveals that the procedure was compiled in thumb mode :
LANG=C armv7a-hardfloat-linux-gnueabi-objdump -d SeriousRatingSeriousRating: file format elf32-littlearmDisassembly of section .text:00008074 <_start>: 8074: e3a00000 mov r0, #0 8078: fa000002 blx 8088 <seriousrating> 807c: e3a00000 mov r0, #0 8080: e3a07001 mov r7, #1 8084: ef000000 svc 0x0000000000008088 <seriousrating>: 8088: b500 push {lr} 808a: 4904 ldr r1, [pc, #16] ; (809c <seriousrating+0x14>) 808c: b100 cbz r0, 8090 <seriousrating+0x8> 808e: 3106 adds r1, #6 8090: 2001 movs r0, #1 8092: 2205 movs r2, #5 8094: 2704 movs r7, #4 8096: df00 svc 0 8098: bd00 pop {pc} 809a: 0000 .short 0x0000 809c: 000090a0 .word 0x000090a0
LANG=C armv7a-hardfloat-linux-gnueabi-objdump -d SeriousRating
SeriousRating: file format elf32-littlearm
Disassembly of section .text:
00008074 <_start>:
8074: e3a00000 mov r0, #0
8078: fa000002 blx 8088 <seriousrating>
807c: e3a00000 mov r0, #0
8080: e3a07001 mov r7, #1
8084: ef000000 svc 0x00000000
00008088 <seriousrating>:
8088: b500 push {lr}
808a: 4904 ldr r1, [pc, #16] ; (809c <seriousrating+0x14>)
808c: b100 cbz r0, 8090 <seriousrating+0x8>
808e: 3106 adds r1, #6
8090: 2001 movs r0, #1
8092: 2205 movs r2, #5
8094: 2704 movs r7, #4
8096: df00 svc 0
8098: bd00 pop {pc}
809a: 0000 .short 0x0000
809c: 000090a0 .word 0x000090a0
However, if I try to use it as a library, splitting it like follows, it fails :
LibSerious.s
.data ratings: .ascii "Booh\n\0Yay!\n" // Print Yay if the argument is positive // Else print booh .text .syntax unified .thumb .globl seriousrating seriousrating: push {lr} ldr r1, =ratings cbz r0, .Lprint adds r1, #6 .Lprint: movs r0, #1 // fd : stdout movs r2, #5 // size : The two strings have the same size movs r7, #4 // Linux : sys_write svc #0 pop {pc}
TestLibSerious.s
.syntax unified .globl _start _start: mov r0, #0 bl seriousrating mov r0, #0 mov r7, #1 svc #0
And then compile it like his :
$ armv7a-hardfloat-linux-gnueabi-as -o LibSerious.o LibSerious.s$ armv7a-hardfloat-linux-gnueabi-as -o TestLibSerious.o TestLibSerious.s$ armv7a-hardfloat-linux-gnueabi-ld.gold -shared --dynamic-linker=/system/bin/linker --hash-style=sysv -o libserious.so LibSerious.o$ armv7a-hardfloat-linux-gnueabi-ld.gold --dynamic-linker=/system/bin/linker --hash-style=sysv -o TestLibSerious TestLibSerious.o libserious.so$ adb push TestLibSerious /data/local/tmp$ adb push libserious.so /data/local/tmp$ adb shell(Android shell) $ cd /data/local/tmp$ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:." ./TestLibSerious
$ armv7a-hardfloat-linux-gnueabi-as -o LibSerious.o LibSerious.s
$ armv7a-hardfloat-linux-gnueabi-as -o TestLibSerious.o TestLibSerious.s
$ armv7a-hardfloat-linux-gnueabi-ld.gold -shared --dynamic-linker=/system/bin/linker --hash-style=sysv -o libserious.so LibSerious.o
$ armv7a-hardfloat-linux-gnueabi-ld.gold --dynamic-linker=/system/bin/linker --hash-style=sysv -o TestLibSerious TestLibSerious.o libserious.so
$ adb push TestLibSerious /data/local/tmp
$ adb push libserious.so /data/local/tmp
$ adb shell
(Android shell)
$ cd /data/local/tmp
$ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:." ./TestLibSerious
I get :
Illegal Instruction(Shell return code : 132)
Illegal Instruction
(Shell return code : 132)
However, if I edit LibSerious.s, remove ".thumb" to get ARM code, replace the cbz by "cmp r0, #0" - "beq .Lprint" and recompile, it works :
.data ratings: .ascii "Booh\n\0Yay!\n" // Print Yay if the argument is positive // Else print booh .text .syntax unified .globl seriousrating seriousrating: push {lr} ldr r1, =ratings cmp r0, #0 beq .Lprint adds r1, #6 .Lprint: movs r0, #1 // fd : stdout movs r2, #5 // size : The two strings have the same size movs r7, #4 // Linux : sys_write svc #0 pop {pc}
$ armv7a-hardfloat-linux-gnueabi-as -o LibSerious.o LibSerious.s$ armv7a-hardfloat-linux-gnueabi-ld.gold -shared --dynamic-linker=/system/bin/linker --hash-style=sysv -o libserious.so LibSerious.o$ adb push libserious.so /data/local/tmp$ adb shell /data/local/tmp(remote)$ cd /data/local/tmp$ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:." ./TestLibSeriousBooh
$ adb shell /data/local/tmp
(remote)
So the question is : Is it possible to write an exported library procedure in Thumb ? If yes how to call it correctly ? Knowing that the caller do not know that procedure is written in Thumb.
Hi myy,
you should also add ".thumb_func" directive before the label "seriousrating".I don't have the Android environment and I experimented with the ordinary gcc.
case 1: wrong caseLibSerious.s:
.data ratings: .ascii "Booh\n\0Yay!\n" .text .syntax unified .thumb //.thumb_func .globl seriousrating seriousrating: push {lr} ldr r1, =ratings cbz r0, .Lprint adds r1, #6 .Lprint: movs r0, #1 // fd : stdout movs r2, #5 // size : The two strings have the same size movs r7, #4 // Linux : sys_write svc #0 pop {pc}
arm-none-eabi-as -o LibSerious.o LibSerious.sarm-none-eabi-as -o TestLibSerious.o TestLibSerious.sarm-none-eabi-ld -o a.out TestLibSerious.o LibSerious.oarm-none-eabi-objdump -D a.out
00008000 <_start>: 8000: e3a00000 mov r0, #0 8004: eb000002 bl 8014 <seriousrating> 8008: e3a00000 mov r0, #0 800c: e3a07001 mov r7, #1 8010: ef000000 svc 0x00000000 00008014 <seriousrating>: 8014: b500 push {lr} 8016: 4904 ldr r1, [pc, #16] ; (8028 <seriousrating+0x14>) 8018: b100 cbz r0, 801c <seriousrating+0x8> 801a: 3106 adds r1, #6 801c: 2001 movs r0, #1 801e: 2205 movs r2, #5 8020: 2704 movs r7, #4 8022: df00 svc 0 8024: bd00 pop {pc} 8026: 002c0000 eoreq r0, ip, r0 802a: Address 0x0000802a is out of bounds.
case 2: good caseLibSerious.s:
.data ratings: .ascii "Booh\n\0Yay!\n" .text .syntax unified .thumb .thumb_func .globl seriousrating seriousrating: push {lr} ldr r1, =ratings cbz r0, .Lprint adds r1, #6 .Lprint: movs r0, #1 // fd : stdout movs r2, #5 // size : The two strings have the same size movs r7, #4 // Linux : sys_write svc #0 pop {pc}
00008000 <_start>: 8000: e3a00000 mov r0, #0 8004: fa000002 blx 8014 <seriousrating> 8008: e3a00000 mov r0, #0 800c: e3a07001 mov r7, #1 8010: ef000000 svc 0x00000000 00008014 <seriousrating>: 8014: b500 push {lr} 8016: 4904 ldr r1, [pc, #16] ; (8028 <seriousrating+0x14>) 8018: b100 cbz r0, 801c <seriousrating+0x8> 801a: 3106 adds r1, #6 801c: 2001 movs r0, #1 801e: 2205 movs r2, #5 8020: 2704 movs r7, #4 8022: df00 svc 0 8024: bd00 pop {pc} 8026: 002c0000 eoreq r0, ip, r0 802a: Address 0x0000802a is out of bounds.
Best regards,
Yasuhiko Koumoto.
Thanks ! That clearly solved the problem. I didn't know the .thumb_func directive.
Indeed, just putting .thumb and .thumb_func before the library procedure label, like in the case 2 you provided, assembling and just linking the library made the untouched executable work like a charm.
Meaning just doing :
$ armv7a-hardfloat-linux-gnueabi-as -o LibSerious.o LibSerious.s$ armv7a-hardfloat-linux-gnueabi-ld.gold -shared --dynamic-linker=/system/bin/linker --hash-style=sysv -o libserious.so LibSerious.o$ adb push libserious.so /data/local/tmp$ adb shell(Phone's shell)$ cd /data/local/tmp$ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:." ./TestLibSerious
(Phone's shell)
provided the expected result :
And TestLibSerious was still using the library, as demonstrated by :
$ adb pull /data/local/tmp/TestLibSerious$ LANG=C armv7a-hardfloat-linux-gnueabi-objdump -d TestLibSerious
$ adb pull /data/local/tmp/TestLibSerious
$ LANG=C armv7a-hardfloat-linux-gnueabi-objdump -d TestLibSerious
TestLibSerious: file format elf32-littlearm Disassembly of section .plt: 000081a0 <seriousrating@plt-0x14>: 81a0: e52de004 push {lr} ; (str lr, [sp, #-4]!) 81a4: e59fe004 ldr lr, [pc, #4] ; 81b0 <seriousrating@plt-0x4> 81a8: e08fe00e add lr, pc, lr 81ac: e5bef008 ldr pc, [lr, #8]! 81b0: 000010ac andeq r1, r0, ip, lsr #1 000081b4 <seriousrating@plt>: 81b4: e28fc600 add ip, pc, #0, 12 81b8: e28cca01 add ip, ip, #4096 ; 0x1000 81bc: e5bcf0ac ldr pc, [ip, #172]! ; 0xac Disassembly of section .text: 000081c0 <_start>: 81c0: e3a00000 mov r0, #0 81c4: ebfffffa bl 81b4 <seriousrating@plt> 81c8: e3a00000 mov r0, #0 81cc: e3a07001 mov r7, #1 81d0: ef000000 svc 0x00000000
So I guess it adds some information about the procedure in the ELF headers, so that the linker knows it has to switch to Thumb mode when reaching the procedure's address ?
In any case, thank you !
Myy