Table of Contents
Picking apart the Bytecode of a hello world program compiled with QuakeC .
Input Program
// print is the builtin function #1 void (string str) print = #1; void() main = { print("hello world"); };
I'm using the
print
builtin of qcvm and the original
qcc
for
compiling.
Header
00000000: 0600 0000 5054 0000 7400 0000 0400 0000 ....PT..t....... 00000010: 0001 0000 0400 0000 2001 0000 0100 0000 ........ ....... 00000020: 9400 0000 0300 0000 3c00 0000 3800 0000 ........<...8... 00000030: 2801 0000 1f00 0000 0000 0000 (...........
-
4 bytes, version:
0x06
-
2 bytes, CRC:
0x5054
Note : Values in big-endian.
Block | Offset | Size |
---|---|---|
Statements |
0x0074
|
0x0004
|
Defs |
0x0100
|
0x0004
|
Fields |
0x0120
|
0x0001
|
Functions |
0x0094
|
0x0003
|
Strings |
0x003c
|
0x0038
|
Globals |
0x0128
|
0x001f
|
Strings
56 bytes of NULL-terminated strings.
00000030: 0074 6573 .tes 00000040: 742e 7163 0070 7269 6e74 0068 656c 6c6f t.qc.print.hello 00000050: 2077 6f72 6c64 006d 6169 6e00 7072 696e world.main.prin 00000060: 7400 6d61 696e 0049 4d4d 4544 4941 5445 t.main.IMMEDIATE 00000070: 0000 0000 ....
We see that the string table generated by the compiler is not optimal
as
print
and
main
are included twice, once for use in the function
section and once for use in the definitions section.
Statements
4 statements, 8 bytes each.
00000070: 0000 0000 0000 0000 2000 1e00 ........ ... 00000080: 0400 0000 3400 1c00 0000 0000 0000 0000 ....4........... 00000090: 0000 0000 ....
OpCode | Arg1 | Arg2 | Arg3 |
---|---|---|---|
DONE | |||
STORE_V |
0x1e
|
0x04
|
|
CALL_1 |
0x1c
|
||
DONE |
In the globals section, we'll see that global
0x1e
has value
0x0f
and global
0x1c
has value
0x01
.
0x04
is the offset of the first parameter.
0x0f
is the offset of "hello world" in the string section.
Again, the code generated by
qcc
is not optimal, the argument is not
a vector so
STORE_F
would be sufficient (assuming
STORE_F
is
faster than
STORE_V
).
See QuakeC Bytecode Opcodes for a list all opcodes.
Functions
3 functions, 36 bytes each.
00000090: 0000 0000 0000 0000 0000 0000 ............ 000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 000000b0: 0000 0000 0000 0000 ffff ffff 1d00 0000 ................ 000000c0: 0000 0000 0000 0000 0900 0000 0100 0000 ................ 000000d0: 0100 0000 0100 0000 0000 0000 0100 0000 ................ 000000e0: 1e00 0000 0000 0000 0000 0000 1b00 0000 ................ 000000f0: 0100 0000 0000 0000 0000 0000 0000 0000 ................
Entry | 1st Local | Locals | Name | File | n-args | argsize |
---|---|---|---|---|---|---|
0 |
0x00
|
0 | EMPTY | EMPTY | 0 | |
-1 |
0x1d
|
0 | test.qc | 1 | 1 | |
1 |
0x1e
|
0 | main | test.qc | 0 |
Definitions
4 definitions, 8 bytes each.
00000100: 0000 0000 0000 0000 0600 1c00 2000 0000 ............ ... 00000110: 0600 1d00 2600 0000 0100 1e00 2b00 0000 ....&.......+...
Type | Global | Offset | Name |
---|---|---|---|
void | no |
0x00
|
EMPTY |
function | no |
0x1c
|
|
function | no |
0x1d
|
main |
string | no |
0x1e
|
IMMEDIATE |
Fields
1 field(s), 8 bytes each.
00000120: 0000 0000 0000 0000 ........
Type | Offset | Name |
---|---|---|
void |
0x00
|
EMPTY |
Globals
31 globals, 4 bytes each.
00000120: 0000 0000 0000 0000 ........ 00000130: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000140: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000150: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000160: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000170: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000180: 0000 0000 0000 0000 0000 0000 0000 0000 ................ 00000190: 0000 0000 0000 0000 0100 0000 0200 0000 ................ 000001a0: 0f00 0000 ....
Global | Value |
---|---|
0x00
…
0x1b
|
0x00
|
0x1c
|
0x01
|
0x1d
|
0x02
|
0x1e
|
0x0f
|
The globals
0x00
to
0x1b
are reserved:
-
0x00
,NULL
-
0x01
, return value -
0x04
, parameter 1 -
0x07
, parameter 2 -
0x0a
, parameter 3 -
0x0d
, parameter 4 -
0x10
, parameter 5 -
0x13
, parameter 6 -
0x16
, parameter 7 -
0x19
, parameter 8
Return and parameter globals are spaced in increments of 3 to allow
returning / passing vectors as arguments. For example, when passing a
vector as the first argument, the x component would be stored in
0x04
, y in
0x05
and z in
0x06
.
Interpreting the Program
The program is run by jumping to the entry point of the
main
function at instruction
0x01
.
STORE_V 0x1e 0x04
stores global
0x1e
, a string containing
"hello
world"
, in global
0x04
, the first parameter of a function call.
(Because
qcc
generated the vector variant of
STORE
, it also stores
0x1f
in
0x05
and
0x20
in
0x06
).
CALL_1 0x1c
calls the function defined by global
0x1c
, in this case the
builtin 1, (function entry -1) which takes one argument, the string to
be printed.
The last instruction is
DONE
, ending the execution of the program.