2

When I try to build my source into a 32-bit static executable for Linux with

 nasm -f elf -F dwarf -g loop.asm
 ld -m elf_i386 -o loop loop.o

I get this R_386_8 error, any idea what causes it?

foo.o: in function `loop1':
foo.asm:(.text+0x12): relocation truncated to fit: R_386_8 against `.bss'
foo.o: in function `endend':
foo.asm:(.text+0x2f): relocation truncated to fit: R_386_8 against `.bss'

loop.asm

cr equ 13 
lf equ 10 

section .bss
numA resb 1

section .text

global _start:

mov [numA],byte 0
call loop1
jmp endend
loop1:
xor cx,cx
mov al, $numA
cmp cx, 0x0A
jle else 
inc al
jmp end
else:
dec al
jmp end
end:
mov [$numA], al
inc cx
cmp cx,20
jle loop1

endend:
mov dl,$numA
mov ah,2
int 21h           ; note: DOS system calls won't work in Linux

(Editor's note: this code has multiple bugs; this Q&A is primarily about the one preventing it from linking. But fixing that won't make a working Linux program.)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Mase
  • 85
  • 2
  • 11
  • are you sure you input `.asm` file into `ld`? I don't think that can work, did you meant `.o`? Then show also the `nasm` command, how you assemble the `.asm` into `.o`. You are probably compiling the asm into 64b object file. To get full 32b nasm->ld->binary chain I'm using on *buntu: `nasm -w+all test.asm -l test.lst -f elf32` `ld -m elf_i386 test.o -o test` (for `test.asm`) (if that is the case, this is duplicate of one of many "how to compiler 32b asm in 64b linux" questions) – Ped7g Oct 25 '17 at 09:30
  • Typo, meant loop.o, nasm command: nasm -f elf -F dwarf -g loop.asm – Mase Oct 25 '17 at 09:35
  • I tried to compile your source, and it has actual semantic/syntax problems, some things don't make much sense in x86-32 assembly. I will try to guess what you did want to achieve, and post fixed source. ... I'm sort of particularly confused what you mean by `$numA`, why the dollar sign there? I didn't see that in NASM source yet, the stand-alone dollar is current address of instruction, so something like `$-numA` would mean something to me, but `$numA` is unknown to me. – Ped7g Oct 25 '17 at 09:36
  • *"An identifier may also be prefixed with a $ to indicate that it is intended to be read as an identifier and not a reserved word"* from [docs](http://www.nasm.us/doc/nasmdoc3.html) ... pity I will forgot that, as I instead never use reserved words for identifiers, neat feature. – Ped7g Oct 25 '17 at 09:40
  • I found some code online on how to get loops and such, trying to learn assembly. the program just runs a loop and adds 1 to numa if loop < 10 and decreases it if loop is => 10. – Mase Oct 25 '17 at 09:41
  • 1
    Is your target platform 32b linux? I would guess so from the `ld -m elf_i386`. But your source is trying to finish by calling `int 21h`, which is DOS service, not available under linux. When searching code online, you should rather check the target platform, after some experience you will recognize from source the 16b vs 32b vs 64b x86 asm variants, and also the OS service calls. (`int 21h` is DOS, `int 80h` is mostly 32b linux or expert-only in 64b linux, `syscall` is 64b linux... I think win32 has `int 80h` too, not sure about it, and most of the win people link against some lib like irvine) – Ped7g Oct 25 '17 at 09:45

2 Answers2

5

In NASM, $numA is the same as numA. A leading $ stops the assembler from considering it as a register name. Thus you can write mov eax, [$eax] to load the eax register from a symbol called eax. (So you could link with C which used int eax = 123;)

So mov [$numA], al looks weird, but it's really just mov [numA], al and isn't the source of the error.


You're getting the error from mov dl,$numA which does a mov dl, imm8 of the low byte of the address.

Most times, that's a case of Basic use of immediates vs. square brackets in YASM/NASM x86 assembly - where you meant to load from memory at that address, like movzx edx, byte [numA] or mov dl, [numA].


The linker warns you because the address of numA doesn't fit in 1 byte, so the r_386_8 relocation would have had to truncate the address.

The _8 tells you it's a relocation that asks the linker to fill in 8 bits (1 byte) as an absolute address. (8-bit relative branch displacements have a different relocation type, although normally you'd use a 32-bit displacement for jumping to a symbol in another file.)

The r_386 tells you it's an i386 relocation as opposed to some type of r_x86_64 relocation (which could be absolute or RIP-relative), or a MIPS jump-target relocation (which would need to right-shift the offset by 2). Possibly related: Relocations in the System V gABI (generic ABI, for which the i386 SysV psABI is a "processor supplement").

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Thanks, fixed it! Quick follow up question, endend is used to print numa, do i need to convert it to ascii/vice versa? – Mase Oct 25 '17 at 10:01
  • @malikaasen yes, there's no DOS or [linux system](http://asm.sourceforge.net/syscall.html#p32) call to output integer value. In DOS you can at least output single 8b extended ASCII character, in linux as far as I'm aware only `sys_write` is available, which takes as input only block of bytes stored in memory. – Ped7g Oct 25 '17 at 10:20
  • 1
    @malikaasen: You can link with libc and have printf do it for you. Or you could convert the digits to a string yourself and print that. (**See the multi-digit number FAQ entry in https://stackoverflow.com/tags/x86/info**). BTW, you have an `int 21h` in your code, but you're making an ELF executable. Linux doesn't implement the DOS `int 21h` system call ABI / API at all. See https://stackoverflow.com/questions/2535989/what-are-the-calling-conventions-for-unix-linux-system-calls-on-x86-64. – Peter Cordes Oct 25 '17 at 10:30
  • Related: [Basic use of immediates vs. square brackets in YASM/NASM x86 assembly](https://stackoverflow.com/q/10362511) for the difference between **`mov ax, foo` (address into 16-bit reg = error) vs. `mov ax, [foo]`** load 16 bits from memory at a label address. – Peter Cordes Apr 24 '21 at 08:17
2

The fixed code with comments starting ;* about what did I modify:

;* build commands used to test:
;* nasm -f elf32 -F dwarf -g loop.asm -l loop.lst -w+all
;* ld -m elf_i386 -o loop loop.o

cr equ 13
lf equ 10

section .bss
numA resb 1

section .text

global _start   ;* global directive takes symbol name (without colon)

_start:
;* the actual label you defined as global, and want to start from.

    ;* set memory at numA address to byte zero
    mov [numA],byte 0
    ;* try to call subroutine with loop
    call loop1
    jmp endend

loop1:
    xor cx,cx       ;* loop counter = 0
.real_loop: 
;* you don't want to loop to "loop1" as that will reset CX!
    mov al, [$numA] ; load AL with value from memory at numA address
;* in NASM you must use [] to indicate memory load/store
;* the mov al, $numA tried to put the memory address numA into AL
;* but memory address in x86-32 is 32 bit value, and AL is 8 bit only
;* and you didn't want address, but value any way.
    cmp cx, 0x0A
    jle .else 
    inc al
    jmp .end
.else:
;* I modified all subroutine labels to be "local" starting with dot
;* i.e. ".else" is full label "loop1.else". This practice will allow
;* you to use also ".else" in different subroutines, while global
;* "else:" can be used only once per source file.
    dec al
    jmp .end
.end:
    mov [$numA], al
    inc cx
    cmp cx,20
    jle .real_loop      ;* fix of loop jump (to not reset CX)
    ;* after CX will reach value 21, the CPU will continue here
    ret         ;* so added return from subroutine

endend:
    ;* call linux 32b sys_exit(numA value) to terminate
    ;* return value will be equal to zero-extended [numA] to 32bits
    ;* 8bit -1 = 0xFF -> return value is 255
    movzx   ebx,byte [$numA]
    mov     eax,1
    int     80h

After running this:

nasm -f elf32 -F dwarf -g loop.asm -l loop.lst -w+all
ld -m elf_i386 -o loop loop.o
./loop ; echo $?

The output is expected:

255
Ped7g
  • 16,236
  • 3
  • 26
  • 63