2

I am using a 64 bit linux machine with a x84-elf64-gcc compiler. I have just started low level programming and would like to understand how C code is actually translated into binary. This is mainly for Operating Systems developement, as I know that a processor doesn't understand ELF, or any other format, and only understands binary.

For example, the following c file:

//test.c
int func()
{
    return 0x12345678;
}

When I compile with gcc:

gcc test.c

I get the following error:

(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status

So I'm guessing that there is an issue with the linker. I do:

gcc test.c -c 

I get an ELF object file, and I do an objdump and get the expected:

0000000000000000 <func>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   b8 78 56 34 12          mov    $0x12345678,%eax
   9:   5d                      pop    %rbp
   a:   c3                      retq   

But when I "cross compile" a 32 bit version using the -m32 option and objdump, I get:

hello.o:     file format elf32-i386


Disassembly of section .text:

00000000 <func>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   e8 fc ff ff ff          call   4 <func+0x4>
   8:   05 01 00 00 00          add    $0x1,%eax
   d:   b8 78 56 34 12          mov    $0x12345678,%eax
  12:   5d                      pop    %ebp
  13:   c3                      ret    

Disassembly of section .text.__x86.get_pc_thunk.ax:

00000000 <__x86.get_pc_thunk.ax>:
   0:   8b 04 24                mov    (%esp),%eax
   3:   c3                      ret    

I've read in a previous answer that this has to do with position independent code: undefined reference to `_GLOBAL_OFFSET_TABLE_' in gcc 32-bit code for a trivial function, freestanding OS

Why is there such a change when you compile with the -m32 option? Moreover, I was advised to use the -ffreestanding option when I compile, but it doesn't seem to have an effect here. I've read that -ffreestanding tells the compiler that there is no standard library, so what is -nostdlib then?

Note: I'm relatively new to this hardcore c programming, and I think that the main issue here is that I don't really understand how linkers/compilers work. :(

Suraaj K S
  • 600
  • 3
  • 21

2 Answers2

4

The options control two parts of the process:

  • -freestanding indicates to the compiler that it should be a freestanding one, AFAIK the only effect is disabling some built-in functions like memcpy;

  • -nostdlib indicates that no libraries and no start up files should be linked by default.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • So isn't -nostdlib an option to be used with ld? Why is it used with gcc? – Suraaj K S Jan 06 '20 at 13:01
  • `gcc` is usully used as a wrapper, calling `ld` itself. (`-c`, `-S`, `-E` and perhaps other options stops the process before the link). – AProgrammer Jan 06 '20 at 13:04
  • `-ffreestanding` also prevents some CRT startup and teardown option from being linked IIRC (when passed to `gcc` when linking). – rubenvb Jan 06 '20 at 13:04
  • @rubenvb, it does not seems so, at least when playing a little with `-v`, `-ffreestanding` and `-nostdlib` with the classical hello world. – AProgrammer Jan 06 '20 at 13:14
  • @SuraajKS: `-nostdlib` controls what other files *gcc passes to `ld`*. `ld` on its own doesn't linking any files that aren't on its command line. That's why gcc has to explicitly pass CRT start files and `-lc` to `ld`. – Peter Cordes Jan 06 '20 at 13:18
2

I don't know what -ffreestanding does exactly, that part is a good question.

But unfortunately your question has a big side-track into 32-bit PIE code:

Why is there such a change when you compile with the -m32 option?

Because you left out any -O optimization option, and 32-bit mode doesn't have an EIP-relative addressing mode for data (only relative jumps/calls). So apparently debug-mode always sets up a register as a GOT pointer as a base for addressing static data, even in functions that don't use it.

Always use -fno-pie to turn off that default unless you specifically want to make a PIE executable.

You might also want -mcmodel=kernel - that's a good idea if you're compiling a 64-bit high-half kernel (static addresses can be used a 32-bit sign-extended immediates, but not 32-bit zero extended). But IDK if it does anything for 32-bit code.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • As far as I know, a 32 bit machine does not even have an RIP, isn't it EIP? I've done a bit of assembly programming and I think that simple function calls usually use instruction pointer relative addresses to jump/branch. Why is it said that 32 bit mode doesn't have relative addressing?? – Suraaj K S Jan 06 '20 at 12:50
  • @SuraajKS: The EIP/RIP-relative data addressing mode was new with x86-64. It's not available in 32-bit mode. I guess I could have said "no EIP-relative addressing mode" because if it existed in 32-bit mode that's what it would be. Yes call and jmp are encoded as `rel32` or `rel8`, but that's separate from *data* addressing modes in a ModRM. – Peter Cordes Jan 06 '20 at 13:16
  • So in other words... Instructions like jmp used(could use) relative addresses while memory accesses (eg. mov eax,[location]) used absolute? – Suraaj K S Jan 06 '20 at 14:29
  • @SuraajKS: In 32-bit mode, yes. – Peter Cordes Jan 06 '20 at 14:29