2

I am trying to assemble and link this tiny x86 assembly code in Linux (Ubuntu 18.04 LTS):

;hello.asm
global _start

extern scanf, printf, exit

section .data
    read_name db '%255s', 0
    msg db 'Hello, %s', 0

section .text
_start:
    sub esp, 256
    push esp
    push read_name
    call scanf
    add esp, 8
    push esp
    push msg
    call printf
    add esp, 264
    push dword 0
    call exit

I am using nasm to assemble and ld to link. As you can probably tell, the code uses C functions, so it has to be linked to glibc. Since my code is using a _start, rather than a main, I decided that it would be better to link to a shared library, since the C runtime needs some startup code to run in _start if the binary is linked statically.

The problem is that I cannot get my code to link, most probably because I am not using the correct glibc .so. This is the way I assemble and link:

nasm -f elf32 hello.asm
ld hello.o -o hello -dynamic-linker /lib/libc.so.6 -lc -m elf_i386

The output file gets created, however when I try to run it this is what I get:

./hello
bash: ./hello: No such file or directory

Doing a quick search, it turns out that these are all the libc .sos on my computer:

locate libc.so
/lib/x86_64-linux-gnu/libc.so.6
/snap/core/8268/lib/i386-linux-gnu/libc.so.6
/snap/core/8268/lib/x86_64-linux-gnu/libc.so.6
/snap/core/8689/lib/i386-linux-gnu/libc.so.6
/snap/core/8689/lib/x86_64-linux-gnu/libc.so.6
/snap/core18/1668/lib/i386-linux-gnu/libc.so.6
/snap/core18/1668/lib/x86_64-linux-gnu/libc.so.6
/usr/lib/x86_64-linux-gnu/libc.so

Can anyone tell me how to link to glibc? (I also get the same problem for 64-bit code)

DarkAtom
  • 2,589
  • 1
  • 11
  • 27
  • Link using `gcc -nostartfiles -m32 -o hello hello.o`. Note that if you invoke `ld` directly, it's wrong to place the library file itself on the command line. Provide libraries as `-l` operands only. So `ld -m elf_i386 -o hello hello.o -lc` should do the trick. – fuz Apr 25 '20 at 21:16
  • Thank you! The GCC command works, but LD still produces a "ghost" file that cannot be found when trying to run it. Why don't you post your answer? (btw gcc gives me an error if I use a 64-bit object file instead, ofc without the `-m32`) – DarkAtom Apr 25 '20 at 21:29
  • Try `ldd hello` to see what shared libraries it tries to pull in and post the result. If any of them is not found, the “No such file or directory” error message may obtain. Could also be an incorrect interpreter. – fuz Apr 25 '20 at 21:32
  • Found the culprit, it tries to link `linux-gate.so.1`, however `locate` doesn't show anything about it. However, I don't know what to do about it. – DarkAtom Apr 25 '20 at 21:38
  • 1
    @DarkAtom `linux-gate.so.1` is the vDSO. Short explanation: it's provided by the Linux kernel automatically and doesn't actually exist on disk. There's something else missing that's keeping you from starting it. – Joseph Sible-Reinstate Monica Apr 25 '20 at 21:41
  • @JosephSible-ReinstateMonica I guess it's an incorrectly specified ELF interpreter. No idea about this detail though. – fuz Apr 25 '20 at 21:43
  • See if running `/lib/ld-linux.so.2 ./hello` works. – Joseph Sible-Reinstate Monica Apr 25 '20 at 21:46
  • Yes, it does work! I'm fine with linking with GCC, but I don't understand why it only works with 32-bit. For 64-bit it throws a seg fault. (My 64-bit code might be the culprit here unfortunately) – DarkAtom Apr 25 '20 at 22:02
  • @DarkAtom You should post a new question with your 64-bit code. – Joseph Sible-Reinstate Monica Apr 25 '20 at 22:03
  • That is exactly what I was about to do, but first I am verifying my code because I might be just dumb. – DarkAtom Apr 25 '20 at 22:04
  • @JosephSible-ReinstateMonica Apparently it was another notorious linking error :) – DarkAtom Apr 25 '20 at 23:28

1 Answers1

3

ld's default dynamic linker for i386 is /usr/lib/libc.so.1, which is wrong on most Linux systems today. You did try to override it, but the path you gave wasn't right either. Two options:

  1. Manually pass the right one when you link: ld hello.o -o hello -dynamic-linker /lib/ld-linux.so.2 -lc -m elf_i386
  2. Use gcc to link instead, as fuz mentioned: gcc -nostartfiles -m32 -o hello hello.o

If you're curious how I knew what the right dynamic linker was for option 1, I did it by doing option 2 once first and checking which one it used.

See also Red Hat's Bug 868662 - /lib/ld64.so.1: bad ELF Interpreter: No such file or directory, someone else who had basically the exact same problem you did (but they got a more helpful error message than you did for some reason).


Edit: there's two other potential problems with your code, that could cause problems in real code but just happened not to in this tiny example:

First, as Employed Russian pointed out in a comment, glibc expects that its own initialization code from its crt will run before your application code starts calling its functions. You were lucky; since you made a dynamically-linked binary, the dynamic linker's use of glibc resulted it in it being initialized for you. Had you made a statically-linked binary instead, it wouldn't have worked. To avoid relying on the dynamic linker that way, the easiest solution is to use main as your entry point instead of _start, and then use gcc -m32 -o hello hello.o to link (note we're not using -nostartfiles anymore). In theory you could still use ld directly to link, but it's complicated enough to get right that there's basically no reason to bother.

Second, you're not aligning the stack correctly. You need to make sure it's aligned to a 16-byte boundary before you call other functions. At the beginning of _start (if you still use that for some reason), the stack will already be aligned like that, so you just have to maintain it. At the beginning of main or any other function, the 4-byte return address will have gotten pushed, so you'd need to push 12 more bytes to realign it.

With both of the above fixes, here's your new hello.asm:

;hello.asm
global main

extern scanf, printf, exit

section .data
    read_name db '%255s', 0
    msg db 'Hello, %s', 0

section .text
main:
    sub esp, 260 ; the 4 extra bytes here are padding for alignment. If you wanted to get value out of them, you could use %259s instead of %255s now
    push esp
    push read_name
    call scanf
    add esp, 8
    push esp
    push msg
    call printf
    add esp, 260 ; we pushed 268 bytes so far, but I'm leaving 8 bytes for alignment
    push dword 0
    call exit

Also, now that you're using main and not _start, you can just return from it instead of calling exit. You just need to make sure you put the stack pointer back where it was at the beginning. To do that, replace everything after call printf with this:

    add esp, 268
    xor eax, eax
    ret

Final note: if you're wondering why I did xor eax, eax and not mov eax, 0, see What is the best way to set a register to zero in x86 assembly: xor, mov or and?.

  • Are you sure it's not `/usr/lib/ld.so.1`? – fuz Apr 25 '20 at 23:02
  • @fuz Now that you mention it, a file called `libc` does seem like an unusual choice for a linker name, but that's definitely what `ld` puts in `.interp`. – Joseph Sible-Reinstate Monica Apr 25 '20 at 23:09
  • Wow. That is weird. `a.out` binaries don't have interpreters as far as I'm concerned. That's an implementation detail of ELF. – fuz Apr 25 '20 at 23:10
  • @fuz [`man ld.so`](http://man7.org/linux/man-pages/man8/ld.so.8.html) says "The program ld.so handles a.out binaries, a binary format used long ago. The program ld-linux.so* (/lib/ld-linux.so.1 for libc5, /lib/ld-linux.so.2 for glibc2) handles binaries that are in the more modern ELF format." – Joseph Sible-Reinstate Monica Apr 25 '20 at 23:11
  • Thanks. Seems like I was not aware of the details. – fuz Apr 25 '20 at 23:46
  • 2
    Linking in `libc.so.6` without using libc's `crt0.o` is not at all guaranteed to work. If this answer works, it's only by accident. – Employed Russian Apr 26 '20 at 04:22
  • @EmployedRussian Thanks, edited. Let me know if you see anything I still left out. – Joseph Sible-Reinstate Monica Apr 26 '20 at 21:41
  • 1
    @EmployedRussian: glibc initializes itself if dynamically linked. It "happens" to work because glibc uses constructor-like functions that the dynamic linker calls. It does break with static linking of course, unless your `_start` calls glibc's init functions. It's probably a good idea for this answer to mention that, but IDK if I'd go as far as "happens to work". This hack should continue working in the future on GNU/Linux. I will agree that it's a bit of a hack and not really recommended, though, but details like how glibc actually makes this work are interesting things to learn. – Peter Cordes Apr 26 '20 at 22:23
  • @PeterCordes Thanks, edited that in too. How's it sound now? – Joseph Sible-Reinstate Monica Apr 26 '20 at 22:51
  • 1
    Yes, looks good now. Definitely agreed with the recommendation to always use GCC for linking libc, whether it's `-nostartfiles` with your own `_start` or not for writing your own `main`. I said the same in [Assembling 32-bit binaries on a 64-bit system (GNU toolchain)](https://stackoverflow.com/q/36861903) which is a near duplicate. You might want to specify `-no-pie` though: modern distros usually config gcc so `-pie` is the default, which means you need `call printf wrt ..plt` or whatever, or else you get runtime text relocations. (Or for 64-bit code it just fails.) – Peter Cordes Apr 26 '20 at 23:02