user1652403
user1652403

Reputation: 45

Undefined reference in a custom ELF file, but the symbol is defined in the files symbol table

I have been trying to learn about x86-64 machine code and ELF files. For that purpose i wrote some code to generate an ELF file with some machine code in it. I use a some machine code that i assembled using nasm (it just prints a message and calls the exit syscall, learning to assemble machine code myself comes next) and wrote a C program to write the correct ELF header/Section headers/Symbol table etc. manually into a file.

Now I am trying to link my file (with a single function in it) against another elf file, which I generate via gcc from C code (test.c):

// does not work with or without "extern"
extern void hello();

void _start()
{
  hello();

  // exit system call
  asm(
    "movl $60,%eax;"
    "xorl %ebx,%ebx;"
    "syscall");
}

The output of readelf -a on my ELF file is (hello.o):

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          64 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         9
  Section header string table index: 8

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000280
       0000000000000044  0000000000000000  AX       0     0     16
  [ 2] .rela.text        RELA             0000000000000000  000002c8
       0000000000000030  0000000000000018   I       6     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000300
       0000000000000005  0000000000000000  WA       0     0     16
  [ 4] .bss              NOBITS           0000000000000000  00000310
       0000000000000080  0000000000000000   A       0     0     16
  [ 5] .rodata           PROGBITS         0000000000000000  00000310
       000000000000000d  0000000000000000   A       0     0     16
  [ 6] .symtab           SYMTAB           0000000000000000  00000320
       0000000000000150  0000000000000018           7    14     8
  [ 7] .strtab           STRTAB           0000000000000000  00000470
       0000000000000028  0000000000000000           0     0     1
  [ 8] .shstrtab         STRTAB           0000000000000000  00000498
       000000000000003f  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

There are no section groups in this file.

There are no program headers in this file.

There is no dynamic section in this file.

Relocation section '.rela.text' at offset 0x2c8 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
00000000001a  000500000001 R_X86_64_64       0000000000000000 .rodata + 0
000000000024  00050000000a R_X86_64_32       0000000000000000 .rodata + d

The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.

Symbol table '.symtab' contains 14 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     9: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c
    10: 0000000000000000    68 FUNC    GLOBAL DEFAULT    1 hello
    11: 0000000000000060    13 OBJECT  LOCAL  DEFAULT    5 msg
    12: 000000000000000d     8 NOTYPE  LOCAL  DEFAULT  ABS len
    13: 0000000000000050     5 OBJECT  GLOBAL DEFAULT    3 _test

No version information found in this file.

I have compiled test.c with

gcc -c -nostdlib -fno-asynchronous-unwind-tables test.c -o test.o

to then link with ld test.o hello.o, which unfortunately yields

ld: test.o: in function `_start':
test.c:(.text+0xa): undefined reference to `hello'

even though the hello function is defined in hello.o (note the entry in the symbol table named hello which is in section 1, the .text section, and seems to have the correct size/type/value/bind).

If I compile a file with just void hello(){} in it the same way I compiled test.c, those two object files can obviously be linked. Also, if I generate my own ELF file hello.o as an executable, renaming the hello function to _start it executes just fine. I have been banging my head against the Wall for a while now, and there is two things I would like to know: Obviously I would like to know my issue with the ELF file. But also I would like to know how I can debug such issues in the future. I have tried to build ld from source (cloning the GNU binutils repo) with debugging symbols, but I did not get very far debugging ld itself.

Edit: I have uploaded my elf file here: https://drive.google.com/file/d/1cRNr0VPAjkEbueuWFYwLYbpijVnLySqq/view?usp=sharing

Upvotes: 2

Views: 2189

Answers (1)

Employed Russian
Employed Russian

Reputation: 213385

This was quite hard to debug.

Here is the output from readelf -WSs hello.o for the file you uploaded to Google drive (it doesn't match the info in your question):

There are 9 section headers, starting at offset 0x40:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        0000000000000000 000280 000044 00  AX  0   0 16
  [ 2] .rela.text        RELA            0000000000000000 0002c8 000030 18   I  6   1  8
  [ 3] .data             PROGBITS        0000000000000000 000300 000005 00  WA  0   0 16
  [ 4] .bss              NOBITS          0000000000000000 000310 000080 00   A  0   0 16
  [ 5] .rodata           PROGBITS        0000000000000000 000310 00000d 00   A  0   0 16
  [ 6] .symtab           SYMTAB          0000000000000000 000320 000150 18      7  14  8
  [ 7] .strtab           STRTAB          0000000000000000 000470 000028 00      0   0  1
  [ 8] .shstrtab         STRTAB          0000000000000000 000498 00003f 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

Symbol table '.symtab' contains 14 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    8
     9: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c
    10: 0000000000000000    68 FUNC    GLOBAL DEFAULT    1 hello
    11: 0000000000000060    13 OBJECT  LOCAL  DEFAULT    5 msg
    12: 000000000000000d     8 NOTYPE  LOCAL  DEFAULT  ABS len
    13: 0000000000000050     5 OBJECT  GLOBAL DEFAULT    3 _test

The issue is with the .sh_info value (14) of the .symtab section.

According to documentation, .sh_info for SYMTAB section is supposed to contain "one greater than the symbol table index of the last local symbol (binding STB_LOCAL)."

So the value 14 tells the linker that all symbols in this file are local, and therefore can't possibly be used to resolve any external references to them.

You need to move all LOCAL symbols before GLOBAL ones (here, msg and len would need to move before hello), so that the symbol table looks like this:

...
     9: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c
    10: 0000000000000060    13 OBJECT  LOCAL  DEFAULT    5 msg
    11: 000000000000000d     8 NOTYPE  LOCAL  DEFAULT  ABS len
    12: 0000000000000000    68 FUNC    GLOBAL DEFAULT    1 hello
    13: 0000000000000050     5 OBJECT  GLOBAL DEFAULT    3 _test

and then set .sh_info for the .symtab section to 12.

But also I would like to know how I can debug such issues in the future.

As you've discovered, debugging binutils ld is very hard, partially because it uses libbfd, which is choke-full of macros and is itself very hard to debug.

I debugged this by building Gold from source, which fortunately produced the exact same failure.

Upvotes: 4

Related Questions