i386 Assembly Language trick for storing data in .text
ratfactor.com
ratfactor.com
In newer processors there exists a cache for return addresses Return Stack Buffer (RSB).
But there is a penalty for doing call and never doing ret.
From Intel's Optimization Reference Manual:[2]
"The return address stack mechanism augments the static and dynamic predictors to optimize specifically for calls and returns. It holds 16 entries, which is large enough to cover the call depth of most programs. If there is a chain of more than 16 nested calls and more than 16 returns in rapid succession, performance may degrade.
[...] To enable the use of the return stack mechanism, calls and returns must be matched in pairs. If this is done, the likelihood of exceeding the stack depth in a manner that will impact performance is very low.
This trick is also what I understand retpolines are about:
Citing kernel doc
The kernel can protect itself against consuming poisoned branch target buffer entries by using return trampolines (also known as "retpoline") for all indirect branches. Return trampolines trap speculative execution paths to prevent jumping to gadget code during speculative execution. x86 CPUs with Enhanced Indirect Branch Restricted Speculation (Enhanced IBRS) available in hardware should use the feature to mitigate Spectre variant 2 instead of retpoline. Enhanced IBRS is more efficient than retpoline.
retbleed https://lwn.net/Articles/901834/
[2] https://discourse.llvm.org/t/is-pic-code-defeating-the-branc...
In general this is a "thing" on pipelined processors, especially once you have out-of-order, because "current instruction" starts to smear out across a bunch of instructions. But for CALL the processor has to dump the pipeline and pick a specific return address to put on the stack.
(unless you're MIPS with the branch delay slot nonsense)
Relative branch instructions and calls are pretty common, so flushing the entite pipeline just to get the current PC would be way too expensive.
So pipelined CPUs actually implementate extra resources to track the current PC of every single instruction (at least as far as the execute stage) just so they can get the PC rapidly.
And you will find almost every single modern CPU has an instruction to copy the current PC to a register.
Modern Intel processors have a "stack engine" (similar to the return stack buffer) that speeds up access through RSP. But regardless, there's no need to dump any pipeline; a call just has an implicit store to memory and the RSP gets updated (register-renamed, really). Calls are very, very fast these days.
In 64-bit Intel/AMD CPUs, the instruction LEA (load effective address), with an address relative to RIP, can be used to save the instruction pointer in a register, then a jump to any address will arrive there providing the old instruction pointer in the register, ready to be used without popping it from the stack.
In all modern pipelined CPUs there are multiple copies of the instruction pointer, one that runs ahead providing the instruction fetch addresses, and an older value that provides base addresses for relative jumps and for relative loads or stores.
It's actually not? Or, indeed, it's a hard problem. And modern designs certainly don't expose the instruction counter as a general register anymore (ARM32 PC is the last of its kind).
But having an IP-relative data addressing mode is a critical feature for any reasonable modern device, for exactly the reasons detailed here: you want constants stored with your compiled code without having to incur overhead (c.f. the linked article, or the GOT/PLT indirection in shared libraries, etc...) to get it.
ARMv7: you can just use PC directly as a register
ARMv8: adr Rd, #0 will move PC into Rd
RISC-V: auipc Rd, 0 will move PC into Rd
Usually what implementers tend to do to avoid piping around massive 64-bit addresses for no good reason is that they form fetch groups (a linear series of instructions starting at a base PC) and then number instructions both by their group ID and their position in the group. The base PC for each group is then stored in an array indexed over the fetch group. If an instruction later needs its PC (such as to perform a branch or because it caused an exception), a request is sent to the fetch group array to fetch the base PC for the operation which can then be used in conjunction with the offset to reconstruct the original PC.
I think you could play nice by actually doing the ret instead of poping EIP. Something like
GET_EIP:
mov eax, [esp]
ret
And then call GET_EIP[1] doubles the effective size of the instruction cache and also makes dual pipelines easier to implement because they can both fetch from the same bus at the same time. Legend has it that this was successful enough to be the inspiration behind thumb mode on ARM, which is also a 32 bit ISA with 16 bit instructions.
Other ISAs from the minicomputer age (PDP-11) and their descendants and inspirations (H8, 68k) had it. Zilog added PC relative loads and address calculations to the Z8000, and it's a generally popular form now in x64.
The Unix V6 assembly source code is very readable because of this and also I think it was unfortunately responsible for the 0-terminated string use because of the ease of writing it that way.
https://developer.arm.com/documentation/ddi0308/d/Thumb-Inst...
Specifically, it can encode any 8-bit value rotated by any number of bits, as well as any value of the form 0x00XY00XY, 0xXY00XY00, or 0xXYXYXYXY. Combined with the use of inverted instructions (e.g. MVN instead of MOV, SUB instead of AND, BIC instead of AND, etc), this covers a surprising number of the 32-bit constants which are likely to appear in a program.
Also see sstrip for ELF files and this legendary writeup https://www.muppetlabs.com/~breadbox/software/tiny/teensy.ht...
On Linux there's a stub subroutine that does exactly that: __i686.get_pc_thunk.<reg>.
Here's the entire subroutine:
__i686.get_pc_thunk.bx:
MOV EBX, DWORD PTR [ESP]
RET
Yup, that's all. If you compile with gcc -m32 -fPIC, you'll see a call to that thunk whenever a function accesses GOT or other relocatable symbols. call 1f
1: <next instruction>So commonly I hadn't considered that people thought getting the EIP on x86 was an obstacle.
> call 1f > > 1: <next instruction>
It's worth noting that "call $+5" degrades performance if used improperly. Consider:
call 1f
1:
pop %eax
See the problem? (Hint: it's branch prediction)That's right! The CPU has its own idea of an approximate "call stack" (a.k.a. return stack buffer), which it uses to predict branch target for RETs.
Note that the POP doesn't count as a "return," so it usually does not pop from the return stack buffer at all. In this case, the CPU confidently mispredicts the branch when the next matching RET gets queued into the pipeline, sometimes severly degrading performance.
It used to confuse a lot of disassemblers, where you'd have to re-synchronize the disassembly after the string and disambiguate between 'code' and 'data' by hand.
For instance on the KC85/2..4 operating system (CAOS) the equivalent of "puts()" expects the "syscall index" and zero-terminated text to print after the call instruction, e.g.:
CALL 0F003H ; call into generic "syscall" entry
DEFB 23H ; "syscall" identifier
DEFM 'HELLO WORLD!'
DEFW 0D0AH ; newline
DEFB 00 ; end of text
NOP ; execution continues here
The syscall dispatcher would pop the return address from the stack and that way discover the data. Before the syscall returns, a modified return address which points to the first byte after the data is pushed back on the stack.Only downside of this approach was that disassemblers would get terribly confused, unless they had specific knowledge about this CAOS peculiarity.
https://archive.org/details/The_Official_GEOS_Programmers_Re...
addr: _LoadSeg
dc.w segmentNumber
dc.w segmentOffset
The _LoadSeg trap would ensure that 'CODE'(segmentNumber) was loaded from disk and then modify the jump code @addr to become an absolute JMP (0x4ef9 + 32 bits) and then set the PC back to @addr and return from the trap. There was also an _UnloadSeg mechanism that would reverse this! print:
pop si
.loop:
lodsb
jz .end
mov ah, 0x0E
xor bh, bh
int 10h
jmp .loop
.end:
push si
ret
I’m writing this from memory, so there may be an off by one error in the above code.It’s used like this, with a null terminated string, rather than a hardcoded length:
call print
db "hello, world", 0
This can even be transformed into something like puts "hello, world"
with the aid of NASM macros. I can’t recall where I saw this trick originally. Maybe some FreeDOS or GRUB code. call next
next:
pop ax
I've used the same approach for printing "inline" strings myself, though in my case I tend to be working with CP/M and there the string are terminated with "$".Fortunately, I’m pretty sure the CPUs that were intended to run this code were rather more efficient at interrupts (in terms of cycles) than modern x86 monsters.
Intel is at least trying to fix this with FRED.
For comparison to the protected-mode version you've outlined: in real-mode, there's no GDT (all segments are 64 kilobytes in length, with their base address given by the value of the segment register shifted left by 4 bits), and the IDT is actually the IVT, a 1024-byte list of 256 segment:offset addresses that begins at memory address zero. There's no switching from user mode to kernel mode. And all IRET has to do is pop flags, segment and offset from the stack, and jump to it.
In the code above, the biggest bottleneck would be probably be the (VGA)BIOS's implementation of the print character function, which was usually dog slow, as it would look up the current cursor position by querying VGA registers using the x86 port IO instructions, which were quite slow even at the time, and would have to do so for each character.
I wouldn't call it fast, but it's good enough for displaying a few short messages onscreen in an assembly-language program, for example a bootloader.
ProDOS came up with this new call syntax where the parameters to the API follow the call to it.
For example:
ldx #$00
ldy #$10
sty params+4
stx params+5 ; setup number of bytes to read (16)
jsr $BF00 ; call ProDOS
.BYTE $CA ; ProDOS command number = CA (read)
.WORD params ; address of parameter table, lo/hi
bcs error ; carry set, error
.
.
params .BYTE $04 ; number of parameters for a read
.BYTE $00 ; file reference number, 0, 1, 2 in MacQForth
.WORD BUFFER ; pointer to data buffer
.WORD $0000 ; requested number of bytes to read, fill in
.WORD $0000 ; number actually read, returned by ProDOSBasically one of the addressing mode is "access word pointed to by register+offset and post-increment the register by word size (2 bytes)".
That can be used to pop off a word from the stack if the register is the stack pointer, but if the register is the program counter, that basically reads the word following the instruction and causes the CPU to continue execution after the immediate data.
A truly orthogonal instruction set :-)
It's generally considered a bad idea from a security standpoint
#pragma comment(linker,"/MERGE:.data=.text /MERGE:.rdata=.text /MERGE:.flat=.text /SECTION:.text,EWR ")But it's a good hint, I hope I didn't sound overly negative.
mov edx, print
... in the example.
global _start
_start:
jmp next
string:
db `Hello World!\n`
len: equ $ - string
next:
mov ecx, string
mov edx, len
mov ebx, 1
mov eax, 4
int 80h
mov ebx, 0
mov eax, 1
int 80h
For NASM, it can also be put into a macro, for example printing to video memory at 0xb8000: %macro print 1
mov ecx, %%loop_start - %%strdata
mov eax, 0x0700
jmp %%loop_start
%%strdata: db %1
%%loop_start:
mov al, [%%strdata + ecx - 1]
mov [0xb8000 + ecx * 2 - 2], ax
loop %%loop_start
%endmacroThis example uses a fixed symbolic reference ("string:") and is the normal way to do it. The trick is to it in a PC relative way.
Though i386 is unlikely to ever become execute-only.
Give the PaX project some credit, since they had it before OpenBSD did. Windows has had it for a while also, since XP.
I didn’t know that and cannot find anything that confirms this. You have a source?
On recent Intel processors, it is possible to execute-only protect pages using Intel MPK (Memory-Protection Keys) by having pages with a key be read-only in the page table but "access disable" in the PKRU register. PKRU is accessible from user mode though.
AFAIK, the only (still) mainstream CPU arch with reliable execute-only protection is RISC-V. (I would like to be wrong, and see it on e.g. ARM as well)
I believe you can still buy new 8086 class and 286 class 16bit x86 cores in random SoCs.
And I know you can buy SoCs with 386 class x86 cores. It might be more accurate to describe them as 486 class cores that don't implement the full 486 instruction set.
The most significant (non-SIMD) change was adding in CMOV.