9

I've been looking at a tutorial for assembly, and I'm trying to get a hello world program to run. I am using Bash on Ubuntu on Windows.

Here is the assembly:

section .text
    global _start     ;must be declared for linker (ld)

_start:             ;tells linker entry point
    mov edx,len     ;message length
    mov ecx,msg     ;message to write
    mov ebx,1       ;file descriptor (stdout)
    mov eax,4       ;system call number (sys_write)
    int 0x80        ;call kernel

    mov eax,1       ;system call number (sys_exit)
    int 0x80        ;call kernel

section .data
    msg db 'Hello, world!', 0xa  ;string to be printed
    len equ $ - msg     ;length of the string

I am using these commands to create the executable:

nasm -f elf64 hello.asm -o hello.o
ld -o hello hello.o -m elf_x86_64

And I run it using:

./hello

The program then seems to run without a segmentation fault or error, but it produces no output.

I can't figure out why the code won't produce an output, but I wonder if using Bash on Ubuntu on Windows has anything to do with it? Why doesn't it produce output and how can I fix it?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Zack
  • 874
  • 1
  • 9
  • 18
  • 7
    The fact that you're using the 32-bit system call interface in a 64-bit executable may have something to do with it. – Ross Ridge Dec 10 '17 at 06:55
  • 4
    I've read that Ubuntu-on-Windows doesn't support 32-bit executables at all; maybe they don't support the `int 0x80` 32-bit ABI in 64-bit executables either. Your code looks like it would work on Linux, where [the `int 0x80` ABI is supported (but *not* recommended) in 64-bit mode](https://stackoverflow.com/questions/46087730/what-happens-if-you-use-the-32-bit-int-0x80-linux-abi-in-64-bit-code). – Peter Cordes Dec 10 '17 at 07:30
  • 1
    Oh, or is `msg` located at an address which doesn't fit into 32 bits, in your 64-bit binary? That would explain it, although that's not the usual layout. Try using `strace ./hello` or single-step with GDB to see the error return value in `eax` – Peter Cordes Dec 10 '17 at 07:32

2 Answers2

17

Related: WSL2 does allow 32-bit user-space programs, WSL1 doesn't. See Does WSL 2 really support 32 bit program? re: making sure you're actually using WSL2. The rest of this answer was written before WLS2 existed.


The issue is with Ubuntu for Windows (Windows Subsystem for Linux version 1). It only supports the 64-bit syscall interface and not the 32-bit x86 int 0x80 system call mechanism.

Besides not being able to use int 0x80 (32-bit compatibility) in 64-bit binaries, Ubuntu on Windows (WSL1) doesn't support running 32-bit executables either. (Same as if you'd built a real Linux kernel without CONFIG_IA32_EMULATION, like some Gentoo users do.)


You need to convert from using int 0x80 to syscall. It's not difficult. A different set of registers are used for a syscall and the system call numbers are different from their 32-bit counterparts. Ryan Chapman's blog has information on the syscall interface, the system calls, and their parameters. Sys_write and Sys_exit are defined this way:

%rax  System call  %rdi               %rsi              %rdx          %r10 %r8 %r9
----------------------------------------------------------------------------------
0     sys_read     unsigned int fd    char *buf         size_t count          
1     sys_write    unsigned int fd    const char *buf   size_t count
60    sys_exit     int error_code     

Using syscall also clobbers RCX and the R11 registers. They are considered volatile. Don't rely on them being the same value after the syscall.

Your code could be modified to be:

section .text
    global _start     ;must be declared for linker (ld)

_start:             ;tells linker entry point
    mov edx,len     ;message length
    mov rsi,msg     ;message to write
    mov edi,1       ;file descriptor (stdout)
    mov eax,edi     ;system call number (sys_write)
    syscall         ;call kernel

    xor edi, edi    ;Return value = 0
    mov eax,60      ;system call number (sys_exit)
    syscall         ;call kernel

section .data
    msg db 'Hello, world!', 0xa  ;string to be printed
    len equ $ - msg     ;length of the string

Note: in 64-bit code if the destination register of an instruction is 32-bit (like EAX, EBX, EDI, ESI etc) the processor zero extends the result into the upper 32-bits of the 64-bit register. mov edi,1 has the same effect as mov rdi,1.


This answer isn't a primer on writing 64-bit code, only about using the syscall interface. If you are interested in the nuances of writing code that calls the C library, and conforms to the 64-bit System V ABI there are reasonable tutorials to get you started like Ray Toal's NASM tutorial. He discusses stack alignment, the red zone, register usage, and a basic overview of the 64-bit System V calling convention.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Michael Petch
  • 46,082
  • 8
  • 107
  • 198
  • 1
    I would also warn about different ABI in 64b, where putting arguments into registers is probably the easy part to grasp, but the stack alignment and red zone may be a bit more tricky for somebody just starting to learn assembly. Generally I would rather suggest to compile as 32b binary in answer to question like this, but that's not an option in windows, where only 64b linux is supported) – Ped7g Dec 10 '17 at 19:46
4

As already pointed out in comments by Ross Ridge, don't use 32-bit calling of kernel functions when you compile 64bit.

Either compile for 32bit or "translate" the code into 64 bit syscalls. Here is what that could look like:

section .text
    global _start     ;must be declared for linker (ld)

_start:             ;tells linker entry point
    mov rdx,len     ;message length
    mov rsi,msg     ;message to write
    mov rdi,1       ;file descriptor (stdout)
    mov rax,1       ;system call number (sys_write)
    syscall         ;call kernel

    mov rax,60      ;system call number (sys_exit)
    mov rdi,0       ;add this to output error code 0(to indicate program terminated without errors)
    syscall         ;call kernel

section .data
    msg db 'Hello, world!', 0xa  ;string to be printed
    len equ $ - msg     ;length of the string
fuz
  • 88,405
  • 25
  • 200
  • 352
  • 1
    Unlike most Linux systems, building as 32-bit is not an option on WSL: the `int 0x80` ABI is just plain not supported, even for 32-bit executables. So it's like a Linux kernel built without `CONFIG_IA32_EMULATION`. – Peter Cordes Jul 21 '18 at 21:45