This question is inspired by a question asked by someone on another forum. In the following code what does the extended inline assembly constraint Rah
and Ral
mean. I haven't seen these before:
#include<stdint.h>
void tty_write_char(uint8_t inchar, uint8_t page_num, uint8_t fg_color)
{
asm (
"int $0x10"
:
: "b" ((uint16_t)page_num<<8 | fg_color),
"Rah"((uint8_t)0x0e), "Ral"(inchar));
}
void tty_write_string(const char *string, uint8_t page_num, uint8_t fg_color)
{
while (*string)
tty_write_char(*string++, page_num, fg_color);
}
/* Use the BIOS to print the first command line argument to the console */
int main(int argc, char *argv[])
{
if (argc > 1)
tty_write_string(argv[1], 0, 0);
return 0;
}
In particular are the use of Rah
and Ral
as constraints in this code:
asm (
"int $0x10"
:
: "b" ((uint16_t)page_num<<8 | fg_color),
"Rah"((uint8_t)0x0e), "Ral"(inchar));
The GCC Documentation doesn't have an l
or h
constraint for either simple constraints or x86/x86 machine constraints. R
is any legacy register and a
is the AX/EAX/RAX register.
What am I not understanding?
What you are looking at is code that is intended to be run in real mode on an x86 based PC with a BIOS. Int 0x10
is a BIOS service that has the ability to write to the console. In particular Int 0x10/AH=0x0e
is to write a single character to the TTY (terminal).
That in itself doesn't explain what the constraints mean. To understand the constraints Rah
and Ral
you have to understand that this code isn't being compiled by a standard version of GCC/CLANG. It is being compiled by a GCC port called ia16-gcc
. It is a special port that targets 8086/80186 and 80286 and compatible processors. It doesn't generate 386 instructions or use 32-bit registers in code generation. This experimental version of GCC is to target 16-bit environments like DOS (FreeDOS, MSDOS), and ELKS.
The documentation for ia16-gcc
is hard to find online in HTML format but I have produced a copy for the recent GCC 6.3.0 versions of the documentation on GitHub. The documentation was produced by building ia16-gcc from source and using make
to generate the HTML. If you review the machine constraints for Intel IA-16—config/ia16 you should now be able to see what is going on:
Ral The al register.
Rah The ah register.
This version of GCC doesn't understand the R
constraint by itself anymore. The inline assembly you are looking at matches that of the parameters for Int 0x10/Ah=0xe:
VIDEO - TELETYPE OUTPUT AH = 0Eh AL = character to write BH = page number BL = foreground color (graphics modes only) Return: Nothing Desc: Display a character on the screen, advancing the cursor and scrolling the screen as necessary
The documentation does list all the constraints that are available for the IA16 target:
Intel IA-16—config/ia16/constraints.md a The ax register. Note that for a byte operand, this constraint means that the operand can go into either al or ah. b The bx register. c The cx register. d The dx register. S The si register. D The di register. Ral The al register. Rah The ah register. Rcl The cl register. Rbp The bp register. Rds The ds register. q Any 8-bit register. T Any general or segment register. A The dx:ax register pair. j The bx:dx register pair. l The lower half of pairs of 8-bit registers. u The upper half of pairs of 8-bit registers. k Any 32-bit register group with access to the two lower bytes. x The si and di registers. w The bx and bp registers. B The bx, si, di and bp registers. e The es register. Q Any available segment register—either ds or es (unless one or both have been fixed). Z The constant 0. P1 The constant 1. M1 The constant -1. Um The constant -256. Lbm The constant 255. Lor Constants 128 … 254. Lom Constants 1 … 254. Lar Constants -255 … -129. Lam Constants -255 … -2. Uo Constants 0xXX00 except -256. Ua Constants 0xXXFF. Ish A constant usable as a shift count. Iaa A constant multiplier for the aad instruction. Ipu A constant usable with the push instruction. Imu A constant usable with the imul instruction except 257. I11 The constant 257. N Unsigned 8-bit integer constant (for in and out instructions).
There are many new constraints and some repurposed ones.
In particular the a
constraint for the AX register doesn't work like other versions of GCC that target 32-bit and 64-bit code. The compiler is free to choose either AH or AL with the a
constraint if the values being passed are 8 bit values. This means it is possible for the a
constraint to appear twice in an extended inline assembly statement.
You could have compiled your code to a DOS EXE with this command:
ia16-elf-gcc -mcmodel=small -mregparmcall -march=i186 \
-Wall -Wextra -std=gnu99 -O3 int10h.c -o int10h.exe
This targets the 80186. You can generate 8086 compatible code by omitting the -march=i186
The generated code for main
would look something like:
00000000 <main>:
0: 83 f8 01 cmp ax,0x1
3: 7e 1d jle 22 <tty_write_string+0xa>
5: 56 push si
6: 89 d3 mov bx,dx
8: 8b 77 02 mov si,WORD PTR [bx+0x2]
b: 8a 04 mov al,BYTE PTR [si]
d: 20 c0 and al,al
f: 74 0d je 1e <tty_write_string+0x6>
11: 31 db xor bx,bx
13: b4 0e mov ah,0xe
15: 46 inc si
16: cd 10 int 0x10
18: 8a 04 mov al,BYTE PTR [si]
1a: 20 c0 and al,al
1c: 75 f7 jne 15 <main+0x15>
1e: 31 c0 xor ax,ax
20: 5e pop si
21: c3 ret
22: 31 c0 xor ax,ax
24: c3 ret
When run with the command line int10h.exe "Hello, world!"
should print:
Hello, world!
Special Note: The IA16 port of GCC is very experimental and does have some code generation bugs especially when higher optimization levels are used. I wouldn't use it for mission critical applications at this point in time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With