I write the assembly code that can be compiled:
as power.s -o power.o
there is on problem when I link the power.o object file:
ld power.o -o power
In order to run on the 64bit OS (Ubuntu 14.04), I added .code32
at the beginning of the power.s
file, however I still get 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
..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 "foreign" machine code that you might want as data, or to export in a shared-memory segment. 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 anypush
orpop
instructions for example.Suggestion: use the
.S
extension for hand-written assembler. (gcc foo.S
will run it through the C preprocessor beforeas
, so you can#include
a header with syscall numbers, for example). Also, it distinguishes it from.s
compiler output (fromgcc foo.c -O3 -S
).To build 32bit binaries, use one of these commands
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 includingprintf(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 linkslibc
(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. Sogcc -nostartfiles hello.S
still lets you callprintf
. For a dynamic executable, the kernel runs/lib/ld-linux.so.2
on it instead of running it directly (usereadelf -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 callprintf
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":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 theint 0x80
ABI.)NASM/YASM
gcc can't handle NASM syntax. (
-masm=intel
is more like MASM than NASM syntax, where you needoffset symbol
to get the address as an immediate). And of course the directives are different (e.g..globl
vsglobal
).You can build with
nasm
oryasm
, then link the.o
withgcc
as above, orld
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 ofa.out
). Use this with-m32
to assemble and link 32bit ELF executables. Not all OSes use ELF, so this script is less portable than usinggcc -nostdlib -m32
to link would be..I prefer yasm for a few reasons, including that it defaults to making long-
nop
s instead of padding with many single-bytenop
s. That makes for messy disassembly output, as well as being slower if the nops ever run. (In NASM, you have to use thesmartalign
macro package.)Example: a program using libc functions from _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. Otherlibc
implementations exist, including MUSL which is designed for static linking and works without initialization calls.)You could also
gdb ./a.out
, and runb _start
,layout reg
,run
, and see what happens.If we'd used
_exit(0)
, or made thesys_exit
system call ourselves withint 0x80
, thewrite(2)
wouldn't have happened. With stdout redirected to a non-tty, it defaults to full-buffered (not line-buffered), so thewrite(2)
is only triggered by thefflush(3)
as part ofexit(3)
. Without redirection, callingprintf(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:
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: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
andRBP
,RSP
registers though.