This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

[ELF/Thumb] Is it possible to create library procedures in Thumb-mode only ?

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

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 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

I get :

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:." ./TestLibSerious

Booh

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.

  • For the record, the only alternative solution I found is to start the procedure in ARM mode and then do a blx jump to a label starting the .thumb code, just after the prologue.

    The epilogue stays unchanged.

    Something like this :

    .syntax unified
    .globl seriousrating
    seriousrating:
      push {lr}
      blx .Lstartsr
      .thumb
    .Lstartsr: // Thumb code
      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} // The ARM processor auto configure the interpretation mode when you move an address to pc
    
    
  • 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 case
    LibSerious.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.s
    arm-none-eabi-as -o TestLibSerious.o TestLibSerious.s
    arm-none-eabi-ld -o a.out TestLibSerious.o LibSerious.o
    arm-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 case
    LibSerious.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.s
    arm-none-eabi-as -o TestLibSerious.o TestLibSerious.s
    arm-none-eabi-ld -o a.out TestLibSerious.o LibSerious.o
    arm-none-eabi-objdump -D a.out

    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

    provided the expected result :

    Booh

    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

    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 !

    Best regards,

    Myy