How to create a statically linked position independent executable ELF in Linux? - linux

I have a working position independent Linux freestanding x86_64 hello world:
main.S
.text
.global _start
_start:
asm_main_after_prologue:
/* Write */
mov $1, %rax /* syscall number */
mov $1, %rdi /* stdout */
lea msg(%rip), %rsi /* buffer */
mov $len, %rdx /* len */
syscall
/* Exit */
mov $60, %rax /* syscall number */
mov $0, %rdi /* exit status */
syscall
msg:
.ascii "hello\n"
len = . - msg
which I can assemble and run with:
as -o main.o main.S
ld -o main.out main.o
./main.out
Since it is position independent due to the RIP relative load, now I wanted to link it as a PIE and see it get loaded at random addresses every time to have some fun.
First I tried:
ld -pie -o main.out main.o
but then running it fails with:
-bash: ./main.out: No such file or directory
and readelf -Wa says that a weird interpreter /lib/ld64.so.1 was used instead of the regular one /lib64/ld-linux-x86-64.so.2 for some reason.
I then learnt that his is actually the recommended System V AMD64
ABI interpreter name at 5.2.1 "Program Interpreter".
In any case, I then try to force matters with:
ld -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -o main.out main.o
and now it works: I get hello and the executable gets loaded to a different address every time according to GDB.
Finally, as a final step, I wanted to also make that executable be statically linked to make things even more minimal, and possibly get rid of the explicit -dynamic-linker.
That's what I could not do, and this is why I'm asking here.
If I try either of:
ld -static -pie -o main.out main.o
ld -static -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -o main.out main.o
-static does not seem to make any difference: I still get dynamic executables.
After quickly glancing at the kernel 5.0 source code in fs/binfmt_elf.c I saw this interesting comment:
* There are effectively two types of ET_DYN
* binaries: programs (i.e. PIE: ET_DYN with INTERP)
* and loaders (ET_DYN without INTERP, since they
* _are_ the ELF interpreter). The loaders must
so I guess when I achieve what I want, I will have a valid interpreter, and I'm so going to use my own minimal hello world as the interpreter of another program.
One thing I might try later on is see how some libc implementation compiles its loader and copy it.
Related question: Compile position-independent executable with statically linked library on 64 bit machine but that mentions an external library, so hopefully this is more minimal and answerable.
Tested in Ubuntu 18.10.

You want to add --no-dynamic-linker to your link command:
$ ld main.o -o main.out -pie --no-dynamic-linker
$ file main.out
main.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped
$ ./main.out
hello
so I guess when I achieve what I want, I will have a valid interpreter, and I'm so going to use my own minimal hello world as the interpreter of another program.
I am not sure I understood what you are saying correctly. If you meant that main.out would have itself as its interpreter, that's wrong.
P.S. GLIBC-2.27 added support for -static-pie, so you no longer have to resort to assembly to get a statically linked PIE binary. But you'll have to use very recent GCC and GLIBC.

Related

(.text+0x0): multiple definition of '_start' [duplicate]

I wrote assembly code that successfully compiles:
as power.s -o power.o
However, it fails when I try to link the object file:
ld power.o -o power
In order to run on the 64 bit OS (Ubuntu 14.04), I added .code32 at the beginning of the power.s file, however I still get the error:
Segmentation fault (core dumped)
power.s:
.code32
.section .data
.section .text
.global _start
_start:
pushl $3
pushl $2
call power
addl $8, %esp
pushl %eax
pushl $2
pushl $5
call power
addl $8, %esp
popl %ebx
addl %eax, %ebx
movl $1, %eax
int $0x80
.type power, #function
power:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %ebx
movl 12(%ebp), %ecx
movl %ebx, -4(%ebp)
power_loop_start:
cmpl $1, %ecx
je end_power
movl -4(%ebp), %eax
imull %ebx, %eax
movl %eax, -4(%ebp)
decl %ecx
jmp power_loop_start
end_power:
movl -4(%ebp), %eax
movl %ebp, %esp
popl %ebp
ret
TL:DR: use gcc -m32 -static -nostdlib foo.S (or equivalent as and ld options).
Or if you don't define your own _start, just gcc -m32 -no-pie foo.S
You may need to install gcc-multilib if you link libc, or however your distro packages /usr/lib32/libc.so, /usr/lib32/libstdc++.so and so on. But if you define your own _start and don't link libraries, you don't need the library package, just a kernel that supports 32-bit processes and system calls. This includes most distros, but not Windows Subsystem for Linux v1.
Don't use .code32
.code32 does not change the output file format, and that's what determines the mode your program will run in. It's up to you to not try to run 32bit code in 64bit mode. .code32 is for assembling kernels that have some 16 and some 32-bit code, and stuff like that. If that's not what you're doing, avoid it so you'll get build-time errors when you build a .S in the wrong mode if it has any push or pop instructions, for example. .code32 just lets you create confusing-to-debug runtime problems instead of build-time errors.
Suggestion: use the .S extension for hand-written assembler. (gcc -c foo.S will run it through the C preprocessor before as, so you can #include <sys/syscall.h> for syscall numbers, for example). Also, it distinguishes it from .s compiler output (from gcc foo.c -O3 -S).
To build 32-bit binaries, use one of these commands
gcc -g foo.S -o foo -m32 -nostdlib -static # static binary with absolutely no libraries or startup code
# -nostdlib still dynamically links when Linux where PIE is the default, or on OS X
gcc -g foo.S -o foo -m32 -no-pie # dynamic binary including the startup boilerplate code.
# Use with code that defines a main(), not a _start
Documentation for nostdlib, -nostartfiles, and -static.
Using libc functions from _start (see the end of this answer for an example)
Some functions, like malloc(3), or stdio functions including printf(3), depend on some global data being initialized (e.g. FILE *stdout and the object it actually points to).
gcc -nostartfiles leaves out the CRT _start boilerplate code, but still links libc (dynamically, by default). On Linux, shared libraries can have initializer sections that are run by the dynamic linker when it loads them, before jumping to your _start entry point. So gcc -nostartfiles hello.S still lets you call printf. For a dynamic executable, the kernel runs /lib/ld-linux.so.2 on it instead of running it directly (use readelf -a to see the "ELF interpreter" string in your binary). When your _start eventually runs, not all the registers will be zeroed, because the dynamic linker ran code in your process.
However, gcc -nostartfiles -static hello.S will link, but crash at runtime if you call printf or something without calling glibc's internal init functions. (see Michael Petch's comment).
Of course you can put any combination of .c, .S, and .o files on the same command line to link them all into one executable. If you have any C, don't forget -Og -Wall -Wextra: you don't want to be debugging your asm when the problem was something simple in the C that calls it that the compiler could have warned you about.
Use -v to have gcc show you the commands it runs to assemble and link. To do it "manually":
as foo.S -o foo.o -g --32 && # skips the preprocessor
ld -o foo foo.o -m elf_i386
file foo
foo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
gcc -nostdlib -m32 is easier to remember and type than the two different options for as and ld (--32 and -m elf_i386). Also, it works on all platforms, including ones where executable format isn't ELF. (But Linux examples won't work on OS X, because the system call numbers are different, or on Windows because it doesn't even use the int 0x80 ABI.)
NASM/YASM
gcc can't handle NASM syntax. (-masm=intel is more like MASM than NASM syntax, where you need offset symbol to get the address as an immediate). And of course the directives are different (e.g. .globl vs global).
You can build with nasm or yasm, then link the .o with gcc as above, or ld directly.
I use a wrapper script to avoid the repetitive typing of the same filename with three different extensions. (nasm and yasm default to file.asm -> file.o, unlike GNU as's default output of a.out). Use this with -m32 to assemble and link 32bit ELF executables. Not all OSes use ELF, so this script is less portable than using gcc -nostdlib -m32 to link would be..
#!/bin/bash
# usage: asm-link [-q] [-m32] foo.asm [assembler options ...]
# Just use a Makefile for anything non-trivial. This script is intentionally minimal and doesn't handle multiple source files
# Copyright 2020 Peter Cordes. Public domain. If it breaks, you get to keep both pieces
verbose=1 # defaults
fmt=-felf64
#ldopt=-melf_i386
ldlib=()
linker=ld
#dld=/lib64/ld-linux-x86-64.so.2
while getopts 'Gdsphl:m:nvqzN' opt; do
case "$opt" in
m) if [ "m$OPTARG" = "m32" ]; then
fmt=-felf32
ldopt=-melf_i386
#dld=/lib/ld-linux.so.2 # FIXME: handle linker=gcc non-static executable
fi
if [ "m$OPTARG" = "mx32" ]; then
fmt=-felfx32
ldopt=-melf32_x86_64
fi
;;
# -static
l) linker="gcc -no-pie -fno-plt -nostartfiles"; ldlib+=("-l$OPTARG");;
p) linker="gcc -pie -fno-plt -nostartfiles"; ldlib+=("-pie");;
h) ldlib+=("-Ttext=0x200800000");; # symbol addresses outside the low 32. data and bss go in range of text
# strace -e raw=write will show the numeric address
G) nodebug=1;; # .label: doesn't break up objdump output
d) disas=1;;
s) runsize=1;;
n) use_nasm=1 ;;
q) verbose=0 ;;
v) verbose=1 ;;
z) ldlib+=("-zexecstack") ;;
N) ldlib+=("-N") ;; # --omagic = read+write text section
esac
done
shift "$((OPTIND-1))" # Shift off the options and optional --
src=$1
base=${src%.*}
shift
#if [[ ${#ldlib[#]} -gt 0 ]]; then
# ldlib+=("--dynamic-linker" "$dld")
#ldlib=("-static" "${ldlib[#]}")
#fi
set -e
if (($use_nasm)); then
# (($nodebug)) || dbg="-g -Fdwarf" # breaks objdump disassembly, and .labels are included anyway
( (($verbose)) && set -x # print commands as they're run, like make
nasm "$fmt" -Worphan-labels $dbg "$src" "$#" &&
$linker $ldopt -o "$base" "$base.o" "${ldlib[#]}")
else
(($nodebug)) || dbg="-gdwarf2"
( (($verbose)) && set -x # print commands as they're run, like make
yasm "$fmt" -Worphan-labels $dbg "$src" "$#" &&
$linker $ldopt -o "$base" "$base.o" "${ldlib[#]}" )
fi
# yasm -gdwarf2 includes even .local labels so they show up in objdump output
# nasm defaults to that behaviour of including even .local labels
# nasm defaults to STABS debugging format, but -g is not the default
if (($disas));then
objdump -drwC -Mintel "$base"
fi
if (($runsize));then
size $base
fi
I prefer YASM for a few reasons, including that it defaults to making long-nops instead of padding with many single-byte nops. That makes for messy disassembly output, as well as being slower if the nops ever run. (In NASM, you have to use the smartalign macro package.)
However, YASM hasn't been maintained for a while and only NASM has AVX512 support; these days I more often just use NASM.
Example: a program using libc functions from _start
# hello32.S
#include <asm/unistd_32.h> // syscall numbers. only #defines, no C declarations left after CPP to cause asm syntax errors
.text
#.global main # uncomment these to let this code work as _start, or as main called by glibc _start
#main:
#.weak _start
.global _start
_start:
mov $__NR_gettimeofday, %eax # make a syscall that we can see in strace output so we know when we get here
int $0x80
push %esp
push $print_fmt
call printf
#xor %ebx,%ebx # _exit(0)
#mov $__NR_exit_group, %eax # same as glibc's _exit(2) wrapper
#int $0x80 # won't flush the stdio buffer
movl $0, (%esp) # reuse the stack slots we set up for printf, instead of popping
call exit # exit(3) does an fflush and other cleanup
#add $8, %esp # pop the space reserved by the two pushes
#ret # only works in main, not _start
.section .rodata
print_fmt: .asciz "Hello, World!\n%%esp at startup = %#lx\n"
$ gcc -m32 -nostdlib hello32.S
/tmp/ccHNGx24.o: In function `_start':
(.text+0x7): undefined reference to `printf'
...
$ gcc -m32 hello32.S
/tmp/ccQ4SOR8.o: In function `_start':
(.text+0x0): multiple definition of `_start'
...
Fails at run-time, because nothing calls the glibc init functions. (__libc_init_first, __dl_tls_setup, and __libc_csu_init in that order, according to Michael Petch's comment. Other libc implementations exist, including MUSL which is designed for static linking and works without initialization calls.)
$ gcc -m32 -nostartfiles -static hello32.S # fails at run-time
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, BuildID[sha1]=ef4b74b1c29618d89ad60dbc6f9517d7cdec3236, not stripped
$ strace -s128 ./a.out
execve("./a.out", ["./a.out"], [/* 70 vars */]) = 0
[ Process PID=29681 runs in 32 bit mode. ]
gettimeofday(NULL, NULL) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
You could also gdb ./a.out, and run b _start, layout reg, run, and see what happens.
$ gcc -m32 -nostartfiles hello32.S # Correct command line
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=7b0a731f9b24a77bee41c13ec562ba2a459d91c7, not stripped
$ ./a.out
Hello, World!
%esp at startup = 0xffdf7460
$ ltrace -s128 ./a.out > /dev/null
printf("Hello, World!\n%%esp at startup = %#lx\n", 0xff937510) = 43 # note the different address: Address-space layout randomization at work
exit(0 <no return ...>
+++ exited (status 0) +++
$ strace -s128 ./a.out > /dev/null # redirect stdout so we don't see a mix of normal output and trace output
execve("./a.out", ["./a.out"], [/* 70 vars */]) = 0
[ Process PID=29729 runs in 32 bit mode. ]
brk(0) = 0x834e000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
.... more syscalls from dynamic linker code
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
mmap2(NULL, 1814236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xfffffffff7556000 # map the executable text section of the library
... more stuff
# end of dynamic linker's code, finally jumps to our _start
gettimeofday({1461874556, 431117}, NULL) = 0
fstat64(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0 # stdio is figuring out whether stdout is a terminal or not
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0xff938870) = -1 ENOTTY (Inappropriate ioctl for device)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7743000 # 4k buffer for stdout
write(1, "Hello, World!\n%esp at startup = 0xff938fb0\n", 43) = 43
exit_group(0) = ?
+++ exited with 0 +++
If we'd used _exit(0), or made the sys_exit system call ourselves with int 0x80, the write(2) wouldn't have happened. With stdout redirected to a non-tty, it defaults to full-buffered (not line-buffered), so the write(2) is only triggered by the fflush(3) as part of exit(3). Without redirection, calling printf(3) with a string containing newlines will flush immediately.
Behaving differently depending on whether stdout is a terminal can be desirable, but only if you do it on purpose, not by mistake.
I'm learning x86 assembly (on 64-bit Ubuntu 18.04) and had a similar problem with the exact same example (it's from Programming From the Ground Up, in chapter 4 [http://savannah.nongnu.org/projects/pgubook/ ]).
After poking around I found the following two lines assembled and linked this:
as power.s -o power.o --32
ld power.o -o power -m elf_i386
These tell the computer that you're only working in 32-bit (despite the 64-bit architecture).
If you want to use gdb debugging, then use the assembler line:
as --gstabs power.s -o power.o --32.
The .code32 seems to be unnecessary.
I haven't tried it your way, but the gnu assembler (gas) also seems okay with:
.globl start
# (that is, no 'a' in global).
Moreover, I'd suggest you probably want to keep the comments from the original code as it seems to be recommended to comment profusely in assembly. (Even if you are the only one to look at the code, it'll make it easier to figure out what you were doing if you look at it months or years later.)
It would be nice to know how to alter this to use the 64-bit R*X and RBP, RSP registers though.

GCC promotes weird relocation R_X86_64_32S error but not in manually linking

I'm trying to invoke sys_write via the syscall instruction under Linux environment on an x86_64 machine with inline assembly in C files. To achieve this, I wrote the following code:
[tjm#ArchPad poc]$ cat poc.c
const char S[] = "abcd\n";
int main() {
__asm __volatile(" \
movq $1, %%rax; \
movq $1, %%rdi; \
movq %0, %%rsi; \
movq $5, %%rdx; \
syscall;"
:
: "i"(S)
);
return 0;
}
While my compiling it with GCC, the following error promoted:
[tjm#ArchPad poc]$ gcc -O0 poc.c -o poc
/usr/bin/ld: /tmp/ccWYxmSb.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status
Then I added -fPIE to the compiler command. Unfortunately, nothing changed:
[tjm#ArchPad poc]$ gcc -fPIE -O0 poc.c -o poc
/usr/bin/ld: /tmp/ccdK36lx.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE
collect2: error: ld returned 1 exit status
This is so weird that it made me curious to compile and link it manually:
[tjm#ArchPad poc]$ gcc -fPIE -O0 poc.c -S
[tjm#ArchPad poc]$ as -O0 poc.s -o poc.o
[tjm#ArchPad poc]$ ld -O0 -melf_x86_64 --dynamic-linker /usr/lib/ld-linux-x86-64.so.2 /usr/lib/crt{1,i,n}.o -lc poc.o -o poc
Surprisingly, no error occurred during the attempt above. I then tested the executable's functionality, which seems working:
[tjm#ArchPad poc]$ ./poc
abcd
Any ideas of why this happens and how to prevent GCC from promoting the error above?
The immediate move instruction movq $S, %rsi, despite taking a 64-bit register, takes only a 32-bit signed immediate. When building a position-independent executable which is to run with ASLR, as is the default on most Linux systems, your program is typically not located in the low or high 31 bits of virtual memory, so the address of a global or static object like S won't fit in a signed 32-bit immediate.
One fix is to use movabs instead, which does take a full 64-bit immediate. Replacing movq %0, %%rsi with movabs %0, %%rsi lets the code build. However, the better approach is to use RIP-relative addressing lea S(%rip), %rsi, which is a shorter instruction and avoids the need for a relocation at load time. This is a little awkward to do inside inline asm, so you can let the compiler load the address into the register for you, with an input operand like "S" (S) (confusingly the constraint for the rsi register is S, which happens to coincide with the name you chose for your array variable).
When you linked manually, you built a non-position-independent executable, which meant that treating the address as a constant worked. You can get the same effect by passing -no-pie to gcc.
There is another serious problem with your code, in that it doesn't declare the registers it clobbers - not only those which you explicitly mov into, but also rcx and r11 which syscall modifies implicitly. You should take a look at How to invoke a system call via syscall or sysenter in inline assembly? which has a correct example. It would be wise to study those examples carefully - GCC inline asm is powerful, but also very easy to get wrong in subtle ways that the compiler will not help you detect, and which may appear to work in a simple program but fail unpredictably in a more complex one.
GCC supports multiple dialects for the ASM instructions and the default can vary. Read more here:
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Extended-Asm
... and of course, the dialects are not compatible with each other.

Linker error when calling printf from _start [duplicate]

This question already has answers here:
Assembling 32-bit binaries on a 64-bit system (GNU toolchain)
(2 answers)
Closed 6 years ago.
I tried to write simple program without main
segment .data
fmt db "test", 0xa, 0
segment .text
global _start
extern printf
_start:
lea rdi, [fmt] ; print simple string
xor eax, eax
call printf
mov eax, 60 ; exit successfully
xor edi, edi
syscall
Compile:
yasm -f elf64 main.s; ld -o main main.o
Got
main.o: In function `_start':
main.s:(.text+0xb): undefined reference to `printf'
How should one fix this?
Cause of Your Error
The reason you get undefined reference to printf while linking is because you didn't link against the C library. As well, when using your own _start label as an entry point there are other issues that must be overcome as discussed below.
Using LIBC without C Runtime Startup Code
To make sure you are using the appropriate dynamic linker at runtime and link against the C library you can use GCC as a frontend for LD. To supply your own _start label and avoid the C runtime startup code use the -nostartfiles option. Although I don't recommend this method, you can link with:
gcc -m64 -nostartfiles main.o -o main
If you wish to use LD you can do it by using a specific dynamic linker and link against the C library with -lc. To link x86_64 code you can use:
ld -melf_x86_64 --dynamic-linker=/lib64/ld-linux-x86-64.so.2 main.o -lc -o main
If you had been compiling and linking for 32-bit, then the linking could have been done with:
ld -melf_i386 --dynamic-linker=/lib/ld-linux.so.2 main.o -lc -o main
Preferred Method Using C Runtime Startup Code
Since you are using the C library you should consider linking against the C runtime. This method also works if you were to create a static executable. The best way to use the C library is to rename your _start label to main and then use GCC to link:
gcc -m64 main.o -o main
Doing it this way allows you to exit your program by returning (using RET) from main the same way a C program ends. This also has the advantage of flushing standard output when the program exits.
Flushing output buffers / exit
Using syscall with EAX=60 to exit won't flush the output buffers. Because of this you may find yourself having to add a newline character on your output to see output before exiting. This is still not a guarantee you will see what you output with printf. Rather than the sys_exit SYSCALL you might consider calling the C library function exit. the MAN PAGE for exit says:
All open stdio(3) streams are flushed and closed. Files created by tmpfile(3) are removed.
I'd recommend replacing the sys_exit SYSCALL with:
extern exit
xor edi, edi ; In 64-bit code RDI is 1st argument = return value
call exit
The printf function is provided by the C standard library, libc. To use it, you need to pass -lc to the linker:
ld -o main main.o -lc
I recommend you to use the C compiler as a frontend for the linker to get possible other flags right, too:
cc -o main -nostartfiles main.o
Note that since your program doesn't initialize the C standard library, e parts of it may not work as expected. If you want to use the C standard library in your assembly program, I recommend you to make your assembly program start from main and to link it by invoking the C compiler:
yasm -f elf64 main.s
cc -o main main.o

My simple asm program segment fault [duplicate]

I wrote assembly code that successfully compiles:
as power.s -o power.o
However, it fails when I try to link the object file:
ld power.o -o power
In order to run on the 64 bit OS (Ubuntu 14.04), I added .code32 at the beginning of the power.s file, however I still get the error:
Segmentation fault (core dumped)
power.s:
.code32
.section .data
.section .text
.global _start
_start:
pushl $3
pushl $2
call power
addl $8, %esp
pushl %eax
pushl $2
pushl $5
call power
addl $8, %esp
popl %ebx
addl %eax, %ebx
movl $1, %eax
int $0x80
.type power, #function
power:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %ebx
movl 12(%ebp), %ecx
movl %ebx, -4(%ebp)
power_loop_start:
cmpl $1, %ecx
je end_power
movl -4(%ebp), %eax
imull %ebx, %eax
movl %eax, -4(%ebp)
decl %ecx
jmp power_loop_start
end_power:
movl -4(%ebp), %eax
movl %ebp, %esp
popl %ebp
ret
TL:DR: use gcc -m32 -static -nostdlib foo.S (or equivalent as and ld options).
Or if you don't define your own _start, just gcc -m32 -no-pie foo.S
You may need to install gcc-multilib if you link libc, or however your distro packages /usr/lib32/libc.so, /usr/lib32/libstdc++.so and so on. But if you define your own _start and don't link libraries, you don't need the library package, just a kernel that supports 32-bit processes and system calls. This includes most distros, but not Windows Subsystem for Linux v1.
Don't use .code32
.code32 does not change the output file format, and that's what determines the mode your program will run in. It's up to you to not try to run 32bit code in 64bit mode. .code32 is for assembling kernels that have some 16 and some 32-bit code, and stuff like that. If that's not what you're doing, avoid it so you'll get build-time errors when you build a .S in the wrong mode if it has any push or pop instructions, for example. .code32 just lets you create confusing-to-debug runtime problems instead of build-time errors.
Suggestion: use the .S extension for hand-written assembler. (gcc -c foo.S will run it through the C preprocessor before as, so you can #include <sys/syscall.h> for syscall numbers, for example). Also, it distinguishes it from .s compiler output (from gcc foo.c -O3 -S).
To build 32-bit binaries, use one of these commands
gcc -g foo.S -o foo -m32 -nostdlib -static # static binary with absolutely no libraries or startup code
# -nostdlib still dynamically links when Linux where PIE is the default, or on OS X
gcc -g foo.S -o foo -m32 -no-pie # dynamic binary including the startup boilerplate code.
# Use with code that defines a main(), not a _start
Documentation for nostdlib, -nostartfiles, and -static.
Using libc functions from _start (see the end of this answer for an example)
Some functions, like malloc(3), or stdio functions including printf(3), depend on some global data being initialized (e.g. FILE *stdout and the object it actually points to).
gcc -nostartfiles leaves out the CRT _start boilerplate code, but still links libc (dynamically, by default). On Linux, shared libraries can have initializer sections that are run by the dynamic linker when it loads them, before jumping to your _start entry point. So gcc -nostartfiles hello.S still lets you call printf. For a dynamic executable, the kernel runs /lib/ld-linux.so.2 on it instead of running it directly (use readelf -a to see the "ELF interpreter" string in your binary). When your _start eventually runs, not all the registers will be zeroed, because the dynamic linker ran code in your process.
However, gcc -nostartfiles -static hello.S will link, but crash at runtime if you call printf or something without calling glibc's internal init functions. (see Michael Petch's comment).
Of course you can put any combination of .c, .S, and .o files on the same command line to link them all into one executable. If you have any C, don't forget -Og -Wall -Wextra: you don't want to be debugging your asm when the problem was something simple in the C that calls it that the compiler could have warned you about.
Use -v to have gcc show you the commands it runs to assemble and link. To do it "manually":
as foo.S -o foo.o -g --32 && # skips the preprocessor
ld -o foo foo.o -m elf_i386
file foo
foo: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, not stripped
gcc -nostdlib -m32 is easier to remember and type than the two different options for as and ld (--32 and -m elf_i386). Also, it works on all platforms, including ones where executable format isn't ELF. (But Linux examples won't work on OS X, because the system call numbers are different, or on Windows because it doesn't even use the int 0x80 ABI.)
NASM/YASM
gcc can't handle NASM syntax. (-masm=intel is more like MASM than NASM syntax, where you need offset symbol to get the address as an immediate). And of course the directives are different (e.g. .globl vs global).
You can build with nasm or yasm, then link the .o with gcc as above, or ld directly.
I use a wrapper script to avoid the repetitive typing of the same filename with three different extensions. (nasm and yasm default to file.asm -> file.o, unlike GNU as's default output of a.out). Use this with -m32 to assemble and link 32bit ELF executables. Not all OSes use ELF, so this script is less portable than using gcc -nostdlib -m32 to link would be..
#!/bin/bash
# usage: asm-link [-q] [-m32] foo.asm [assembler options ...]
# Just use a Makefile for anything non-trivial. This script is intentionally minimal and doesn't handle multiple source files
# Copyright 2020 Peter Cordes. Public domain. If it breaks, you get to keep both pieces
verbose=1 # defaults
fmt=-felf64
#ldopt=-melf_i386
ldlib=()
linker=ld
#dld=/lib64/ld-linux-x86-64.so.2
while getopts 'Gdsphl:m:nvqzN' opt; do
case "$opt" in
m) if [ "m$OPTARG" = "m32" ]; then
fmt=-felf32
ldopt=-melf_i386
#dld=/lib/ld-linux.so.2 # FIXME: handle linker=gcc non-static executable
fi
if [ "m$OPTARG" = "mx32" ]; then
fmt=-felfx32
ldopt=-melf32_x86_64
fi
;;
# -static
l) linker="gcc -no-pie -fno-plt -nostartfiles"; ldlib+=("-l$OPTARG");;
p) linker="gcc -pie -fno-plt -nostartfiles"; ldlib+=("-pie");;
h) ldlib+=("-Ttext=0x200800000");; # symbol addresses outside the low 32. data and bss go in range of text
# strace -e raw=write will show the numeric address
G) nodebug=1;; # .label: doesn't break up objdump output
d) disas=1;;
s) runsize=1;;
n) use_nasm=1 ;;
q) verbose=0 ;;
v) verbose=1 ;;
z) ldlib+=("-zexecstack") ;;
N) ldlib+=("-N") ;; # --omagic = read+write text section
esac
done
shift "$((OPTIND-1))" # Shift off the options and optional --
src=$1
base=${src%.*}
shift
#if [[ ${#ldlib[#]} -gt 0 ]]; then
# ldlib+=("--dynamic-linker" "$dld")
#ldlib=("-static" "${ldlib[#]}")
#fi
set -e
if (($use_nasm)); then
# (($nodebug)) || dbg="-g -Fdwarf" # breaks objdump disassembly, and .labels are included anyway
( (($verbose)) && set -x # print commands as they're run, like make
nasm "$fmt" -Worphan-labels $dbg "$src" "$#" &&
$linker $ldopt -o "$base" "$base.o" "${ldlib[#]}")
else
(($nodebug)) || dbg="-gdwarf2"
( (($verbose)) && set -x # print commands as they're run, like make
yasm "$fmt" -Worphan-labels $dbg "$src" "$#" &&
$linker $ldopt -o "$base" "$base.o" "${ldlib[#]}" )
fi
# yasm -gdwarf2 includes even .local labels so they show up in objdump output
# nasm defaults to that behaviour of including even .local labels
# nasm defaults to STABS debugging format, but -g is not the default
if (($disas));then
objdump -drwC -Mintel "$base"
fi
if (($runsize));then
size $base
fi
I prefer YASM for a few reasons, including that it defaults to making long-nops instead of padding with many single-byte nops. That makes for messy disassembly output, as well as being slower if the nops ever run. (In NASM, you have to use the smartalign macro package.)
However, YASM hasn't been maintained for a while and only NASM has AVX512 support; these days I more often just use NASM.
Example: a program using libc functions from _start
# hello32.S
#include <asm/unistd_32.h> // syscall numbers. only #defines, no C declarations left after CPP to cause asm syntax errors
.text
#.global main # uncomment these to let this code work as _start, or as main called by glibc _start
#main:
#.weak _start
.global _start
_start:
mov $__NR_gettimeofday, %eax # make a syscall that we can see in strace output so we know when we get here
int $0x80
push %esp
push $print_fmt
call printf
#xor %ebx,%ebx # _exit(0)
#mov $__NR_exit_group, %eax # same as glibc's _exit(2) wrapper
#int $0x80 # won't flush the stdio buffer
movl $0, (%esp) # reuse the stack slots we set up for printf, instead of popping
call exit # exit(3) does an fflush and other cleanup
#add $8, %esp # pop the space reserved by the two pushes
#ret # only works in main, not _start
.section .rodata
print_fmt: .asciz "Hello, World!\n%%esp at startup = %#lx\n"
$ gcc -m32 -nostdlib hello32.S
/tmp/ccHNGx24.o: In function `_start':
(.text+0x7): undefined reference to `printf'
...
$ gcc -m32 hello32.S
/tmp/ccQ4SOR8.o: In function `_start':
(.text+0x0): multiple definition of `_start'
...
Fails at run-time, because nothing calls the glibc init functions. (__libc_init_first, __dl_tls_setup, and __libc_csu_init in that order, according to Michael Petch's comment. Other libc implementations exist, including MUSL which is designed for static linking and works without initialization calls.)
$ gcc -m32 -nostartfiles -static hello32.S # fails at run-time
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, BuildID[sha1]=ef4b74b1c29618d89ad60dbc6f9517d7cdec3236, not stripped
$ strace -s128 ./a.out
execve("./a.out", ["./a.out"], [/* 70 vars */]) = 0
[ Process PID=29681 runs in 32 bit mode. ]
gettimeofday(NULL, NULL) = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
You could also gdb ./a.out, and run b _start, layout reg, run, and see what happens.
$ gcc -m32 -nostartfiles hello32.S # Correct command line
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=7b0a731f9b24a77bee41c13ec562ba2a459d91c7, not stripped
$ ./a.out
Hello, World!
%esp at startup = 0xffdf7460
$ ltrace -s128 ./a.out > /dev/null
printf("Hello, World!\n%%esp at startup = %#lx\n", 0xff937510) = 43 # note the different address: Address-space layout randomization at work
exit(0 <no return ...>
+++ exited (status 0) +++
$ strace -s128 ./a.out > /dev/null # redirect stdout so we don't see a mix of normal output and trace output
execve("./a.out", ["./a.out"], [/* 70 vars */]) = 0
[ Process PID=29729 runs in 32 bit mode. ]
brk(0) = 0x834e000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
.... more syscalls from dynamic linker code
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
mmap2(NULL, 1814236, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xfffffffff7556000 # map the executable text section of the library
... more stuff
# end of dynamic linker's code, finally jumps to our _start
gettimeofday({1461874556, 431117}, NULL) = 0
fstat64(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0 # stdio is figuring out whether stdout is a terminal or not
ioctl(1, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0xff938870) = -1 ENOTTY (Inappropriate ioctl for device)
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7743000 # 4k buffer for stdout
write(1, "Hello, World!\n%esp at startup = 0xff938fb0\n", 43) = 43
exit_group(0) = ?
+++ exited with 0 +++
If we'd used _exit(0), or made the sys_exit system call ourselves with int 0x80, the write(2) wouldn't have happened. With stdout redirected to a non-tty, it defaults to full-buffered (not line-buffered), so the write(2) is only triggered by the fflush(3) as part of exit(3). Without redirection, calling printf(3) with a string containing newlines will flush immediately.
Behaving differently depending on whether stdout is a terminal can be desirable, but only if you do it on purpose, not by mistake.
I'm learning x86 assembly (on 64-bit Ubuntu 18.04) and had a similar problem with the exact same example (it's from Programming From the Ground Up, in chapter 4 [http://savannah.nongnu.org/projects/pgubook/ ]).
After poking around I found the following two lines assembled and linked this:
as power.s -o power.o --32
ld power.o -o power -m elf_i386
These tell the computer that you're only working in 32-bit (despite the 64-bit architecture).
If you want to use gdb debugging, then use the assembler line:
as --gstabs power.s -o power.o --32.
The .code32 seems to be unnecessary.
I haven't tried it your way, but the gnu assembler (gas) also seems okay with:
.globl start
# (that is, no 'a' in global).
Moreover, I'd suggest you probably want to keep the comments from the original code as it seems to be recommended to comment profusely in assembly. (Even if you are the only one to look at the code, it'll make it easier to figure out what you were doing if you look at it months or years later.)
It would be nice to know how to alter this to use the 64-bit R*X and RBP, RSP registers though.

Build static ELF without libc using unistd.h from Linux headers

I'm interested in building a static ELF program without (g)libc, using unistd.h provided by the Linux headers.
I've read through these articles/question which give a rough idea of what I'm trying to do, but not quite:
http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
Compiling without libc
https://blogs.oracle.com/ksplice/entry/hello_from_a_libc_free
I have basic code which depends only on unistd.h, of which, my understanding is that each of those functions are provided by the kernel, and that libc should not be needed. Here's the path I've taken that seems the most promising:
$ gcc -I /usr/include/asm/ -nostdlib grabbytes.c -o grabbytesstatic
/usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000400144
/tmp/ccn1mSkn.o: In function `main':
grabbytes.c:(.text+0x38): undefined reference to `open'
grabbytes.c:(.text+0x64): undefined reference to `lseek'
grabbytes.c:(.text+0x8f): undefined reference to `lseek'
grabbytes.c:(.text+0xaa): undefined reference to `read'
grabbytes.c:(.text+0xc5): undefined reference to `write'
grabbytes.c:(.text+0xe0): undefined reference to `read'
collect2: error: ld returned 1 exit status
Before this, I had to manually define SEEK_END and SEEK_SET according to the values found in the kernel headers. Else it would error saying that those were not defined, which makes sense.
I imagine that I need to link into an unstripped vmlinux to provide the symbols to utilize. However, I read through the symbols and while there were plenty of llseeks, they were not llseek verbatim.
So my question can go in a few directions:
How can I specify an ELF file to utilize symbols from? And I'm guessing if/how that's possible, the symbols won't match up. If this is correct, is there an existing header file which will redefine llseek and default_llseek or whatever is exactly in the kernel?
Is there a better way to write Posix code in C without a libc?
My goal is to write or port fairly standard C code using (perhaps solely) unistd.h and invoke it without libc. I'm probably okay without a few unistd functions, and am not sure which ones exist "purely" as kernel calls or not. I love assembly, but that's not my goal here. Hoping to stay as strictly C as possible (I'm fine with a few external assembly files if I have to), to allow for a libc-less static system at some point.
Thank you for reading!
If you're looking to write POSIX code in C, the abandonment of libc is not going to be helpful. Although you could implement a syscall function in assembler, and copy structures and defines from the kernel header, you would essentially be writing your own libc, which almost certainly would not be POSIX compliant. With all the great libc implementations out there, there's almost no reason to begin implementing your own.
dietlibc and musl libc are both frugal libc implementations which yield impressively small binaries The linker is generally smart; as long as a library is written to avoid the accidentally pulling in numerous dependencies, only the functions you use will actually be linked into your program.
Here is a simple hello world program:
#include<unistd.h>
int main(){
char str[] = "Hello, World!\n";
write(1, str, sizeof str - 1);
return 0;
}
Compiling it with musl below yeilds a binary of a less than 3K
$ musl-gcc -Os -static hello.c
$ strip a.out
$ wc -c a.out
2800 a.out
dietlibc produces an even smaller binary, less than 1.5K:
$ diet -Os gcc hello.c
$ strip a.out
$ wc -c a.out
1360 a.out
This is far from ideal, but a little bit of (x86_64) assembler has me down to just under 5KB (but most of that is "other things than code" - the actual code is under 1KB [771 bytes to be precise], but the file size is much larger, I think because the code size is rounded to 4KB, and then some header/footer/extra stuff is added to that]
Here's what I did:
gcc -g -static -nostdlib -o glibc start.s glibc.c -Os -lc
glibc.c contains:
#include <unistd.h>
int main()
{
const char str[] = "Hello, World!\n";
write(1, str, sizeof(str));
_exit(0);
}
start.s contains:
.globl _start
_start:
xor %ebp, %ebp
mov %rdx, %r9
mov %rsp, %rdx
and $~16, %rsp
push $0
push %rsp
call main
hlt
.globl _exit
_exit:
// We known %RDI already has the exit code...
mov $0x3c, %eax
syscall
hlt
That main point of this is not to show that it's not the system call part of glibc that takes up a lot of space, but the "prepar things" - and beware that if you were to call for example printf, possibly even (v)sprintf, or exit(), or any other "standard library" function, you are in the land of "nobody knows what will happen".
Edit: Updated "start.s" to put argc/argv in the right places:
_start:
xor %ebp, %ebp
mov %rdx, %r9
pop %rdi
mov %rsp, %rsi
and $~16, %rsp
push %rax
push %rsp
// %rdi = argc, %rsi=argv
call main
Note that I've changed which register contains what thing, so that it matches main - I had them slightly wrong order in the previous code.

Resources