Trying to link a .c file with a .s file, but I get "Illegal instruction"

711 views Asked by At

I have these two files, 9-main.c and 9-mult.s.

9-main.c :

#include <stdio.h>

int mult(int);

int main(){
        int result = mult(5);
        printf("Result: %d\n",result);
        return 0;
}

9-mult.s :

.global mult

mult:
        mov     r1,r0
        lsl     r0,r0,#4
        add     r0,r1,r0
        bx      lr

I am trying to link them:

$ arm-linux-gnueabihf-gcc 9-main.c 9-mult.s

But if I execute a.out:

$ ./a.out 
Illegal instruction

What could be the reason?

2

There are 2 answers

9
Frant On

You are compiling/executing in a non-trivial environment for a beginner: compiling/linking/executing a mix of aarch32 C/assembly code on a aarch64 system.

I would suggest to start specifying which architecture you are assembling for as well as the exact instruction set you are using - this is useful information for people who want to help - and to use some standard directives for defining functions in GNU AS assembly language, the way they would be generated by the gcc compiler:

        .arch armv7-a            // target architecture is armv7-a/Aarch32
        .fpu vfpv3-d16           // specify available floating-point hardware
        .syntax unified          // use unified assembler syntax
        .arm                     // generate 32 bit Arm code 
        .type   mult, %function  // 'mult' symbol is related to a function
        .global mult             // 'mult' is a global symbol
        .text                    // code will be put into the .text section
mult:
        mov     r1,r0
        lsl     r0,r0,#4
        add     r0,r1,r0
        bx      lr
        .end                     // end of program

 ./a.out
Result: 85

Please refer to the GNU AS documentation for more details.

I am still looking for the root cause of your problem, which was actually fixed by using .type mult, %function. Note that the exact same error does occur on a Cortex-A7Linux system, that is the fact that use are running a 64 bit Linux is not the issue.

Compiling two executables (with/without the .type directive) and comparing the outputs of the readelf -a command shows only one difference:

267c267
<    104: 00000534     0 NOTYPE  GLOBAL DEFAULT   13 mult
---
>    104: 00000534     0 FUNC    GLOBAL DEFAULT   13 mult

UPDATE:

The reason for your problem was pointed-out by old_timer - see his comment hereafter.

Your compiler is generating thumb2/T32 code by default, but your assembly code was arm/A32 by default since you did not specify which instruction set you where using. Compiling you original code with gcc -marm option (arm/A32) works fine:

arm-linux-gnueabihf-gcc-10 -g -marm -o mult mult.c mult.s
./mult
Result: 85

The other option would be to force GNU AS to generate thumb2/T32 code in mult.s:

.thumb
.global mult

mult:
        mov     r1,r0
        lsl     r0,r0,#4
        add     r0,r1,r0
        bx      lr

arm-linux-gnueabihf-gcc-10 -g -o mult mult.c mult.s
./mult
Result: 85

For more details on thumb2/T32, arm/A32 and interwork code, see here and here.

4
old_timer On

You really need to get the arm documentation and learn the instruction set.

You should disassemble your program to confirm. The first question is are you cross compiling this on an x86 and then trying to run ARM on x86? I think you would get a different error. Next is it an a.out file format issue, that may be primitive so trying to run aarch32 in aarch64 mode and would be bad instructions or...

For armv7-a compatibility mode on a 64 bit arm (armv8...) if told that or built for that the gcc compiler will default to thumb/thumb2 mode.

int mult(int);
int fun ( void )
{
    return(mult(5)+1);
}

00000000 <fun>:
   0:   e96d 3e02   strd    r3, lr, [sp, #-8]!
   4:   f04f 0005   mov.w   r0, #5
   8:   f7ff fffe   bl  0 <mult>
   c:   f100 0001   add.w   r0, r0, #1
  10:   bd08        pop {r3, pc}
  12:   bf00        nop

then as written

.global mult
mult:
        mov     r1,r0
        lsl     r0,r0,#4
        add     r0,r1,r0
        bx      lr

will default to arm unless specified otherwise on the command line (which it wasn't)

00000000 <mult>:
   0:   e1a01000    mov r1, r0
   4:   e1a00200    lsl r0, r0, #4
   8:   e0810000    add r0, r1, r0
   c:   e12fff1e    bx  lr

because mult is not marked as a function label in the eyes of gnu linker then when you link it

00002000 <fun>:
    2000:   e96d 3e02   strd    r3, lr, [sp, #-8]!
    2004:   f04f 0005   mov.w   r0, #5
    2008:   f000 f804   bl  2014 <mult>
    200c:   f100 0001   add.w   r0, r0, #1
    2010:   bd08        pop {r3, pc}
    2012:   bf00        nop

00002014 <mult>:
    2014:   e1a01000    mov r1, r0
    2018:   e1a00200    lsl r0, r0, #4
    201c:   e0810000    add r0, r1, r0
    2020:   e12fff1e    bx  lr

boom, there is your fault. bl to mult without a trampoline means it is in thumb mode and now it is trying to execute those bytes as thumb instructions not arm. It might survive for a while but will eventually fault.

(Frant has confirmed this for you, assuming this is the path you took to this particular fault)

So you have a few choices, can read Frant's answer:

.global mult
.type   mult, %function
mult:
        mov     r1,r0
        lsl     r0,r0,#4
        add     r0,r1,r0
        bx      lr

Tell the tools that mult is a function label, re-assemble and re-link:

00002000 <fun>:
    2000:   e96d 3e02   strd    r3, lr, [sp, #-8]!
    2004:   f04f 0005   mov.w   r0, #5
    2008:   f000 e804   blx 2014 <mult>
    200c:   f100 0001   add.w   r0, r0, #1
    2010:   bd08        pop {r3, pc}
    2012:   bf00        nop

00002014 <mult>:
    2014:   e1a01000    mov r1, r0
    2018:   e1a00200    lsl r0, r0, #4
    201c:   e0810000    add r0, r1, r0
    2020:   e12fff1e    bx  lr

Because it is an armv7 we got the blx thumb2 extension, if I specify armv4t across the project then we get the trampoline I was expecting. Both are fine depends on the instruction set, and armv4t will work on the aarch32 compatibility mode side of the 64 bit processor.

00002000 <fun>:
    2000:   b510        push    {r4, lr}
    2002:   2005        movs    r0, #5
    2004:   f000 f80c   bl  2020 <__mult_from_thumb>
    2008:   3001        adds    r0, #1
    200a:   bc10        pop {r4}
    200c:   bc02        pop {r1}
    200e:   4708        bx  r1

00002010 <mult>:
    2010:   e1a01000    mov r1, r0
    2014:   e1a00200    lsl r0, r0, #4
    2018:   e0810000    add r0, r1, r0
    201c:   e12fff1e    bx  lr

00002020 <__mult_from_thumb>:
    2020:   4778        bx  pc
    2022:   e7fd        b.n 2020 <__mult_from_thumb>
    2024:   eafffff9    b   2010 <mult>

The linker adds the mult_from_thumb trampoline to convert between thumb mode and arm mode. bx lr can handle mode switches so you don't need a trampoline on the way out only on the way in.

Your other option would be to build mult as thumb

.thumb
.global mult
mult:
        mov     r1,r0
        lsl     r0,r0,#4
        add     r0,r1,r0
        bx      lr

00002000 <fun>:
    2000:   b510        push    {r4, lr}
    2002:   2005        movs    r0, #5
    2004:   f000 f804   bl  2010 <mult>
    2008:   3001        adds    r0, #1
    200a:   bc10        pop {r4}
    200c:   bc02        pop {r1}
    200e:   4708        bx  r1

00002010 <mult>:
    2010:   1c01        adds    r1, r0, #0
    2012:   0100        lsls    r0, r0, #4
    2014:   1808        adds    r0, r1, r0
    2016:   4770        bx  lr

But this would fault if called from arm mode code.

With gnu assembler you can use .type...function or .thumb_func, to make this more correct.

.thumb
.global mult
.thumb_func
mult:
        mov     r1,r0
        lsl     r0,r0,#4
        add     r0,r1,r0
        bx      lr

00002000 <fun>:
    2000:   b510        push    {r4, lr}
    2002:   2005        movs    r0, #5
    2004:   f000 f804   bl  2010 <mult>
    2008:   3001        adds    r0, #1
    200a:   bc10        pop {r4}
    200c:   bc02        pop {r1}
    200e:   4708        bx  r1

00002010 <mult>:
    2010:   1c01        adds    r1, r0, #0
    2012:   0100        lsls    r0, r0, #4
    2014:   1808        adds    r0, r1, r0
    2016:   4770        bx  lr

Same machine code so the .thumb would have made it work dumb luck, but declaring the label as a function is as important as declaring it as global. With the .type you can put that wherever like you can with .globl but .thumb_func has to be before the label, not immediately but basically the next label it finds it will mark as a function.

So after

arm-linux-gnueabihf-gcc 9-main.c 9-mult.s -o myprog.elf

while you are learning asm, and even sometimes when not, certainly if you are mixing languages you should always follow that with:

arm-linux-gnueabihf-objdump -D myprog.elf

and examine the output.

Can use lowercase -d for just the code, uppercase -D will also include the data sections which are often as important to see that it built what you wanted it to build.

To complete asking your question I recommend you disassemble and post the main function and the mult function to see if it is an interwork problem. Or did you instead try to run an arm binary on an x86 or something like that?

Frant has shown a more verbose set of directives, if you examine the full output of the compiler

    .cpu cortex-a72
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 1
    .eabi_attribute 18, 4
    .file   "so.c"
    .text
    .align  1
    .p2align 2,,3
    .global fun
    .arch armv8-a
    .arch_extension crc
    .syntax unified
    .thumb
    .thumb_func
    .fpu softvfp
    .type   fun, %function
fun:
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    strd    r3, lr, [sp, #-8]!
    mov r0, #5
    bl  mult
    add r0, r0, #1
    pop {r3, pc}
    .size   fun, .-fun
    .ident  "GCC: (GNU) 10.2.0"

You will see among other things the .type function. They double dipped here and did both the .type and .thumb_func which makes sense, this is an algorithmically driven output.