NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: bfr on May 09, 2019, 06:51:33 AM
-
Hi everyone,
I'm trying to use a short relative jump to conditionally skip over a known sequence of instructions. I've calculated that the offset should be 30 bytes (0x1e), given the code and disassembly output below. I want it to jump from from 0x100D to 0x102D, and the offset should be relative to the instruction after the jump instruction (0x100F), so 0x102D - 0x100F = 0x1e.
When I write "jne short 0x1e" and compile the program with NASM, this becomes 751D (jnz +0x1d) and the offset is 1 byte too small. I have no idea why this happens. It work if I instead write "jne short $ + 0x20", which only confuses me more. If anyone could explain why "jne short 0x1e" only jumps by 0x1d bytes, I'd greatly appreciate it!
Here's the complete code:
section .data
SYS_EXIT equ 60
EXIT_CODE equ 0
READ equ 0
WRITE equ 1
STDIN equ 0
STDOUT equ 1
IOCTL equ 16
FDFLUSH equ 10
section .bss
tape: resb 32768
must_flush: resb 1
section .text
global _start
_start:
mov rbx, 0
cmp [must_flush], byte 1
jne short 0x1e // Jump from here
mov rax, IOCTL
mov rdi, STDOUT
mov rsi, FDFLUSH
mov rdx, 0
syscall
mov [must_flush], byte 0
mov rax, READ // Jump to here
mov rdi, STDIN
mov rsi, tape
add rsi, rbx
mov rdx, 1
syscall
_exit:
mov rax, SYS_EXIT
mov rdi, EXIT_CODE
syscall
And here's what I get when I run ndisasm on the compiled program:
00000FFF 00BB00000000 add [rbx+0x0],bh
00001005 803C2500A0400001 cmp byte [0x40a000],0x1
0000100D 751D jnz 0x102c // This should jump to 0x102D
0000100F B810000000 mov eax,0x10
00001014 BF01000000 mov edi,0x1
00001019 BE0A000000 mov esi,0xa
0000101E BA00000000 mov edx,0x0
00001023 0F05 syscall
00001025 C6042500A0400000 mov byte [0x40a000],0x0
0000102D B800000000 mov eax,0x0
00001032 BF00000000 mov edi,0x0
00001037 48BE002040000000 mov rsi,0x402000
-0000
00001041 4801DE add rsi,rbx
00001044 BA01000000 mov edx,0x1
00001049 0F05 syscall
0000104B B83C000000 mov eax,0x3c
00001050 BF00000000 mov edi,0x0
00001055 0F05 syscall
-
I might be missing something, but I always thought that when you use something like a jump instruction in assembly, the parameter you give to the jump instruction is the target [address] - not the relative offset encoded in the instruction. In other words, it's not your task to compute the difference in addresses, it's up to the assembler (which knows the lengths of the instructions, the actual encoding etc.)
Personally, I would interpret "jne short 0x1e" as "jump to the address 0x1e" (not jump 0x1e bytes forward)... so I'm kinda surprised you're jumping even close to the desired location :)
Using the $ operator can fix the problem, yes - but it still seems to be unnecessary.
Why don't you just create a label for the instruction you want to jump to, and then use it?
...
jne short _flush_done
...
_flush_done:
mov rax, READ // Jump to here
-
Thanks very much for your reply!
This is a short relative jump (https://thestarman.pcministry.com/asm/2bytejumps.htm (https://thestarman.pcministry.com/asm/2bytejumps.htm)) -- it's position-independent and has a range of -128 to +127.
The reason I'm not generating a label is that (a) I'm actually using this approach in generating much larger programs where this pattern comes up many, many times, so I'll need to generate lots of labels (not hard, admittedly, but messy); and (b) I'm always going to be jumping over the same sequence of instructions, so the size of the jump is constant.
But even if I switch to using labels (which I'm using for, e.g., loop constructs where the loop body has an unknown size) I'm still curious why this offset has 1 byte subtracted when being converted into a short jump instruction.
-
Notice NASM will optimize your code by default (-Ox, multipass optimization), so, instructions as:
mov rax,0
Will be encoded as
mov eax,0
Without the REX prefix... If you want your code to not be optimized, use -O0 option.
ALL jumps are relative (to RIP), including CALL and JMP instructions, except when absolute jumps are explicit made or indirect ones... In optimized compilation, NASM will try to select shorter instructions, so your conditional jump will use a signed byte offset.
To use an explicit value is dangerous because of optimization. But it seems NASM is wise enough to recalculate it...
-
Ah, I see, that makes a lot of sense. So the only way to produce a short relative jump instruction is to jump to a nearby explicit address, or to use an offset relative to RIP. There's no way to write the short relative jump explicitly and have NASM interpret it as such? Thanks very much!
-
I'd put it this way - you are "interfering" with nasm's job (you are doing part of the compilation yourself and trying to make it accept your computation of the offset as part of its internal work).
If you want to force a specific (pre-computed, pre-compiled) instruction into the code, you can just use
db 75h, 20h
- and you'll have the short "jne" instruction jumping forward by 20h bytes (bypassing nasm altogether for that instruction).
Or, use the $+0x20 expression - so that nasm knows that you are binding the target to the current address; that's basically what you are looking for. Pure "jne 0x20" is, IMHO, just bad syntax - meaning something different from what you want.
-
Thanks for clarifying, I didn't fully appreciate how much work NASM was doing and that this would be interfering with that.
Since the size of the jump is sensitive to optimization, I've switched to using labels. And I've got a much clearer idea of how this all works, thanks to you and Frederico. Much appreciated!