4
votes

I'm trying to write some special routine in assembly, for x86-64 (even x86 example is fine). The problem: my immediates are only resolved at link time.

For example,

addq $Label2-Label1, %rax

will add the difference between the two labels/symbols to rax. Unfortunately, because GNU Assembler only does one pass (I don't know why, even Open Source assemblers like FASM do multiple passes), it cannot resolve those so it will make the linker do it.

Unfortunately, it will reserve 4 bytes (32-bit immediate) for the linker, which is not what I want, because the difference in labels is always within -128 to +127 range.

My question, how do I force or specify that the instruction should have an 8-bit immediate? Like, what's the syntax here? In AT&T syntax or Intel, either is fine. For example, in NASM you do:

add rax, byte Label2-Label1

to specify 8bit immediate. But how to do this in GAS? What's the syntax to force it to use 8-bit immediate even if it doesn't know the immediate itself... I'd ideally want this in GAS, for specific reasons, so please don't tell me to use NASM as an answer!

EDIT: I'm sorry I forgot to mention, that yes, it is a "forward reference" here. Both labels are defined after the instruction, that is why GAS can't resolve them, I thought it's a relocation, but yes using '.byte label2-label1' works for sure as I have tested it, so I know it should be possible if it had some syntax for it...

2
Do those 3 extra bytes make that much of a difference?Drew McGowen
Well, if it is not supposed to make a difference then just use a C compiler. Instruction length is one of the major bottlenecks on x86_64.Hans Passant
Well I thought there was a simple syntax I am missing, and the documentation has absolutely zero info about instruction sets and syntaxes (other than "comparing" it to Intel), or atleast I could not find anything. But again, for example, NASM can do this with 'byte', and uses Intel syntax, so I was wondering what would GAS's syntax be (it doesn't work with 'byte' even if I use Intel syntax in it...) And this kind of instruction is part of some bigger table computing offsets though, repeated many times.kktsuri

2 Answers

4
votes

If Label1 and Label2 are in the same source file then the problem doesn't seem to be related to the linker (GAS doesn't generate any relocations in that case) nor is it really due to GAS being a one pass assembler. It's smart enough to generate correct sized branches, which is a similar problem.

The problem comes down to GAS not being smart enough to choose the right sized instruction in cases other than jumps and branches, and not having any way to explicitly specify the size of the operand. The ".d8" displacement suffix is almost the syntax you want, but you're technically not using a displacement. But it wouldn't work anyways, as leal.d8 Label2-Label1(%eax),%eax doesn't work, despite the fact a displacement is actually being used.

So that leaves really only one alternative, hand assembling the opcode. For example in 32-bit assembly:

.byte 0x83, 0xC0, (label2 - label1)

As for why GAS is only a one pass assembler, and in general doesn't do a number of things other assemblers, the answer is simple. It's primarily designed to assemble the output of GCC.

1
votes

Unfortunately, this is not possible; because GAS doesn't figure out what the difference should be, it has no choice but to leave a 4-byte relocation entry for the linker to resolve. This is because the object file format (which is more than likely ELF) doesn't support 8-bit relocations - it only supports 32-bit relocations. Thus, it will always use a 32-bit immediate.