Search code examples
linuxinterruptassembly

Segmentation fault when call 0x16


Here is piece of my nasm code:

extern printf

%macro print 2
        mov  rdi, %1
        mov  rsi, %2
        mov  rax, 0
        call printf
%endmacro

section .data

        msg1:   db 'Nasm', 0
        len1:   equ $ - msg1
        fmts:   db "%s", 10, 0 ; printf format string 
        fmti:   db "%d", 10, 0

section .bss           ;Uninitialized data
   num resb 5

section  .text
        global main    ; declaring for gcc
main:
        push    rbp            ; save rbp

        print   fmts, msg1
        xor     ah, ah
        int     0x16
        print   fmti, [num]

exit:
        leave
        mov     rax,1       ;system call number (sys_exit)
        int     0x80        ;call kernel

Output:

[b@l .K]$ nasm test.asm -f elf64 -o test.o && gcc test.o -o test && ./test
Nasm
Segmentation fault (core dumped)

Whan I replace:

        print   fmts, msg1
        print   fmti, [num]
        xor     ah, ah
        int     0x16

then

[b@l .K]$ nasm test.asm -f elf64 -o test.o && gcc test.o -o test && ./test
Nasm
0
Segmentation fault (core dumped)

int 0x80 works greatly but 0x16 crush my code. I'm on fedora 29, Intel core i5


Solution

  • int 0x80
    

    The int, sysenter and syscall instructions are special variants of a call instruction:

    These instructions call a special function, a so-called "handler".

    int 0x80 is a handler in the Linux operating system intended for 32-bit Linux programs. Calling int 0x80 from 64-bit programs (and your program obviously is 64-bit) may work, but it may also not work.

    In 64-bit Linux you use syscall instead of int 0x80. The exit system call should (*) look like this:

    mov $60, %rax  # In 64-bit Linux sys_exit is 60, not 1
    mov $0, %rdi   # Exit code; this would be %ebx in 32-bit Linux
    syscall
    

    int 0x16 is a handler in the BIOS. You can only call BIOS handlers from 16-bit real-mode (**) programs. You can neither call this handler from 32- nor from 64-bit programs.


    (*) Unfortunately, I have written assembly programs for 32-bit Linux only, so I'm not sure if this is correct.

    (**) The CPU supports two different operating modes when executing 16-bit code. BIOS handlers will only work in one of these two modes.


    wait for keyboard

    In Linux there are no explicit keyboard functions.

    You have to use the termios functions to switch the behavior of the stdin file handle (file handle 0). In assembler, this would be done by a sys_ioctl call.

    The default behavior is that Linux processes the input line-wise (e.g. if you press "AB"+"backspace"+"CD"+"enter", Linux will return "ACD"+"enter" to the program).

    The default behavior is also that sys_read will wait until some data is available. Using termios you may change this behavior in a way that all keyboard presses are returned to the program and/or that sys_read will not wait for input.

    Then you call sys_read to read from stdin.