DEV Community

ddupard
ddupard

Posted on

MIPS From Source Code to Binary: What Does GCC Actually Do ?

Comparing C source code with its machine-level translation is an invaluable exercise for any developer or security researcher. Compilation is not a mere literal translation; it is an optimization process where the compiler often prioritizes portability and robustness over the use of direct, specialized processor instructions. To illustrate this, we are going to use a simple program which will be compiled for a MIPS processor:

#include <stdio.h>
#include <math.h>



void test_floating_point(float f) {
    // C'est ici qu'on verra si le compilateur génère 'abs.s'
    float res = fabsf(f);
    printf("FABS of %f is %f\n", f, res);
}

int main() {

    test_floating_point(-3.14159f);

    return 0;
}
Enter fullscreen mode Exit fullscreen mode

The commands to compile this program are the following

  mips-linux-gnu-gcc -g -fno-stack-protector -z execstack -no-pie -march=mips32 -mabi=32 -o "$OUT_MIPS/tests/mips1" "$SRC/mips1.c"
  mips-linux-gnu-objdump -d --show-raw-insn -M reg-names=32 "$OUT_MIPS/tests/mips1" > "$OUT_MIPS/tests/mips1.mips"
  mips-linux-gnu-readelf -a "$OUT_MIPS/tests/mips1" > "$OUT_MIPS/tests/mips1.mips_elf"
Enter fullscreen mode Exit fullscreen mode

and below the assembly

00400650 <test_floating_point>:
  400650:   27bdffc8    addiu   sp,sp,-56
  400654:   afbf0034    sw  ra,52(sp)
  400658:   afbe0030    sw  s8,48(sp)
  40065c:   03a0f025    move    s8,sp
  400660:   3c1c0043    lui gp,0x43
  400664:   279c8010    addiu   gp,gp,-32752
  400668:   afbc0018    sw  gp,24(sp)
  40066c:   e7cc0038    swc1    fa0,56(s8)
  400670:   8fc30038    lw  v1,56(s8)
  400674:   3c027fff    lui v0,0x7fff
  400678:   3442ffff    ori v0,v0,0xffff
  40067c:   00621024    and v0,v1,v0
  400680:   afc20020    sw  v0,32(s8)
  400684:   c7c00038    lwc1    fv0,56(s8)
  400688:   460000a1    cvt.d.s fv1,fv0
  40068c:   c7c00020    lwc1    fv0,32(s8)
  400690:   46000021    cvt.d.s fv0,fv0
  400694:   f7a00010    sdc1    fv0,16(sp)
  400698:   f7c20028    sdc1    fv1,40(s8)
  40069c:   8fc7002c    lw  a3,44(s8)
  4006a0:   8fc60028    lw  a2,40(s8)
  4006a4:   3c020040    lui v0,0x40
  4006a8:   24440790    addiu   a0,v0,1936
  4006ac:   8f828024    lw  v0,-32732(gp)
  4006b0:   0040c825    move    t9,v0
  4006b4:   0320f809    jalr    t9
  4006b8:   00000000    nop
  4006bc:   8fdc0018    lw  gp,24(s8)
  4006c0:   00000000    nop
  4006c4:   03c0e825    move    sp,s8
  4006c8:   8fbf0034    lw  ra,52(sp)
  4006cc:   8fbe0030    lw  s8,48(sp)
  4006d0:   27bd0038    addiu   sp,sp,56
  4006d4:   03e00008    jr  ra
  4006d8:   00000000    nop

004006dc <main>:
  4006dc:   27bdffe0    addiu   sp,sp,-32
  4006e0:   afbf001c    sw  ra,28(sp)
  4006e4:   afbe0018    sw  s8,24(sp)
  4006e8:   03a0f025    move    s8,sp
  4006ec:   3c020040    lui v0,0x40
  4006f0:   c44007a4    lwc1    fv0,1956(v0)
  4006f4:   46000306    mov.s   fa0,fv0
  4006f8:   0c100194    jal 400650 <test_floating_point>
  4006fc:   00000000    nop
  400700:   00001025    move    v0,zero
  400704:   03c0e825    move    sp,s8
  400708:   8fbf001c    lw  ra,28(sp)
  40070c:   8fbe0018    lw  s8,24(sp)
  400710:   27bd0020    addiu   sp,sp,32
  400714:   03e00008    jr  ra
  400718:   00000000    nop
  40071c:   00000000    nop

Disassembly of section .MIPS.stubs:

00400720 <_MIPS_STUBS_>:
  400720:   8f998010    lw  t9,-32752(gp)
  400724:   03e07825    move    t7,ra
  400728:   0320f809    jalr    t9
  40072c:   24180009    li  t8,9
  400730:   8f998010    lw  t9,-32752(gp)
  400734:   03e07825    move    t7,ra
  400738:   0320f809    jalr    t9
  40073c:   24180007    li  t8,7
    ...
Enter fullscreen mode Exit fullscreen mode

and a part of the elf

Global entries
Address Access Initial Sym.Val. Type Ndx Name
0042002c -32740(gp) 00000000 00000000 NOTYPE UND _ITM_registerTMCloneTable
00420030 -32736(gp) 00000000 00000000 FUNC UND gmon_start
00420034 -32732(gp) 00400730 00400730 FUNC UND printf
00420038 -32728(gp) 00000000 00000000 NOTYPE UND _ITM_deregisterTMCloneTable
0042003c -32724(gp) 00400720 00400720 FUNC UND __libc_start_main

1. why it is interesting ?

To understand, you have to know that the MIPS® Architecture For Programmers Volume II-A which is the reference for the instructions set of the mips processor contains very specific instructions to calculate the absolute value of a floating point. In fact it contains 3 specific instructions
ABS.S
ABS.D
ABS.PS

Instruction Set

so we should find one of these instructions in the test_floating_point function. we don't find any of these instructions, instead we find

400674: 3c027fff    lui v0,0x7fff       # v0 = 0x7fff0000
400678: 3442ffff    ori v0,v0,0xffff    # v0 = 0x7fffffff
40067c: 00621024    and v0,v1,v0        # v0 = v1 & 0x7fffffff
Enter fullscreen mode Exit fullscreen mode

The compiler applied a fundamental property of the IEEE 754 format here: a 32-bit floating-point number consists of one sign bit, eight exponent bits, and 23 mantissa bits. To calculate the absolute value, it is sufficient to force the sign bit to 0. This is exactly what the AND operation with 0x7FFFFFFF performs.
As seen with this example, the compiler often generates code compatible with multiple FPU variants, or even "Soft-Float" modes, to avoid creating overly strict dependencies on specific hardware.

2. Epilogue

In this very short article we explained a very specific point of what gcc does when it compiles a C source code. This shows why disassembling a binary compiled with gcc is always interesting. As a reverser, this analysis teaches us a crucial lesson: never rely solely on an architecture's theoretical documentation. Real code is the result of a chemistry between the processor manual, the ABI (Application Binary Interface) rules, and the compiler's optimization choices.

Top comments (0)