ECHO
and INPUT
commands), the generated code must somehow call the functions of the operating system. How does he know their addresses?EBP
/ RBP
.RIP
. In order for the code to really turn out to be cross-platform, it is necessary to use explicit relative addressing.EBP
/ RBP
- i.e. Immediately after the communication region, at offset 0x18
, the variable region begins.R01..R04
, which it is natural for these EAX,ECX,EDX,EBX
registers to associate: i.e. the number of this register is obtained from the number of "abstract" by subtracting one. If we wanted to use the rest of the processor registers, the correspondence between the numbers would be more difficult.gcc
transfer up to three parameters in the ECX,EDX,EAX
registers ( fastcall ). On x64, six parameters are transmitted in the registers RDI,RSI,RDX,RCX,R8,R9
, and gcc
does not allow changing the method of the call. Because of this incompatibility, you will have to implement both transfer mechanisms in the code generator, and choose the one you need. Fortunately, passing parameters will be the only platform-specific place in our code.EAX,ECX,EDX
, so they will need to be saved before the call (if they are alive), and restored after. Thus, information about the liveliness of physical registers, which we used during their assignment, is also needed when generating code.#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
const char * fdata = NULL ; //
int input() { int d; scanf( " %d " , &d); return d; }
void echoi( int i) { printf( " %d " , i); }
void echos( int offset) { printf( " %s " , fdata+offset); }
void * linkarea[ 1000 ] = {( void *)input, ( void *)echoi, ( void *) echos};
int main( int argc, char ** argv) {
if (argc!= 2 ) {
printf( "Missing code file name. \n " );
exit( 1 );
}
int fd = open(argv[ 1 ], O_RDONLY);
if (fd< 0 ) {
printf( "Cannot open code file. \n " );
exit( 1 );
}
struct stat finfo;
fstat(fd, &finfo);
fdata = ( const char *)mmap( 0 , finfo.st_size, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0 );
if (!fdata) {
printf( "Cannot read code file. \n " );
exit( 1 );
}
(( void (*)( void **)) fdata)(linkarea); //
munmap(( void *)fdata, finfo.st_size);
close(fd);
return 0 ;
}
#include "jsk.h"
that set the definition of commands disappeared: the new loader knows nothing about the jay script or about our p-code.union
nor field size restrictions are needed. Replace jsk.h
with a regular structure declaration with int
fields:struct command {
enum opcodes {
hlt, store, jz, echo, mov, load, input, add, sub, mul, div,
eq, ne, ge, le, gt, lt
};
opcodes opcode;
regnum dest, src1, src2;
int imm;
command(opcodes opcode, regnum dest, regnum src1, regnum src2) :
opcode(opcode), dest(dest), src1(src1), src2(src2) {}
command(opcodes opcode, regnum dest, int imm) :
opcode(opcode), dest(dest), imm(imm) {}
};
add
n-command can be compiled into MOV
, INC
, DEC
, into one of three ADD
forms or into one of three LEA
forms, depending on the combination of arguments.mul r, r, 2
by add r, r, r
, are conveniently done at the level of the p-code, before being translated into executable code. At the same stage - after choosing the physical registers, but before the final broadcast - we will get rid of the registers whose value is known in all the places where they are used: during the broadcast we will use the known value.// :
// ...
if (i->cmd.opcode==command::mul) { //
if (i->known.count(i->cmd.src1)) std::swap(i->cmd.src1, i->cmd.src2);
if (i->known.count(i->cmd.src2)) switch (i->known[i->cmd.src2]) {
case - 1 : i->cmd = command(command::sub, i->cmd.dest, 0 , i->cmd.src1); break ;
case 0 : i->cmd = command(command::mov, i->cmd.dest, 0 ); break ;
case 1 : nopOut(i); break ;
case 2 : i->cmd = command(command::add, i->cmd.dest, i->cmd.src1, i->cmd.src1); break ;
}
}
// , ,
void postalloc() {
std::vector< bool > needed(lastreg+ 1 );
foreach(i, pcode) {
if (i->has2src()) {
if (!i->known.count(i->cmd.src1)) needed[i->cmd.src1]= true ;
if (!i->known.count(i->cmd.src2)) needed[i->cmd.src2]= true ;
else // src2 :
if (i->cmd.opcode==command::div && i->known[i->cmd.src2]!= 2 )
needed[i->cmd.src2]= true ; //
}
if (i->readsdest() && !i->known.count(i->cmd.dest))
needed[i->cmd.dest]= true ;
}
foreach(i, pcode)
if (i->writesdest() && !needed[i->cmd.dest])
nopOut(i);
}
// :
// ...
postalloc();
// NOP-
simpleopt();
// : , known
foreach(i, pcode) {
std::map<regnum, int > known;
if (i->known.count(i->cmd.dest))
known[physmap[i->cmd.dest]] = i->known[i->cmd.dest];
i->cmd.dest = physmap[i->cmd.dest];
if (i->has2src()) {
if (i->known.count(i->cmd.src1))
known[physmap[i->cmd.src1]] = i->known[i->cmd.src1];
if (i->known.count(i->cmd.src2))
known[physmap[i->cmd.src2]] = i->known[i->cmd.src2];
i->cmd.src1 = physmap[i->cmd.src1];
i->cmd.src2 = physmap[i->cmd.src2];
}
i->known = known;
}
break ;
std::vector<char> code;
struct commandn
we need some new fields. int offset, length;
will determine the position of the command in the code
vector. In addition, for JZ
and ECHO
commands, the offset value in which is unknown until the end of the code generation, in the int needfixat;
field int needfixat;
we will keep the index of the blank offset.code
; then go through the code a second time, and fill in the missing offsets. After that, we output all code and all lines to the result.// : x86/x64
const char regsize = sizeof ( void *); // 4 8
//
if (regsize== 4 ) { // PUSH EBP / MOV EBP, [ESP+8] / PUSH EDI / PUSH ESI / PUSH EBX
code.push_back( 0x55 ); code.push_back( 0x8b ); code.push_back( 0x6c ); code.push_back( 0x24 );
code.push_back( 0x08 ); code.push_back( 0x57 ); code.push_back( 0x56 ); code.push_back( 0x53 );
} else { // PUSH EBP / MOV RBP, RDI / PUSH EBX
code.push_back( 0x55 ); code.push_back( 0x48 ); code.push_back( 0x8b ); code.push_back( 0xef );
code.push_back( 0x53 );
}
foreach(i, pcode) {
int imm, doffset, joffset;
bool jshort, saveAX, saveDX;
i->offset = code.size();
switch (i->cmd.opcode) {
case command::hlt:
if (regsize== 4 ) {
i->emit( 0x5b , 0x5e , 0x5f , 0x5d ); // POP EBX / POP ESI / POP EDI / POP EBP
i->emit( 0xc2 , 2 , 0 ); // RET 2
} else
i->emit( 0x5b , 0x5d , 0xc3 ); // POP EBX / POP EBP / RET
break ;
case command::store: // MOV [EBP+18+(imm-1)*4], dst
doffset = 0x18 +(i->cmd.imm- 1 )* 4 ;
if (i->known.count(i->cmd.dest)) {
i->emit( 0xc7 );
if (doffset< 128 )
i->emit( 0x45 , ( char )doffset);
else {
i->emit14( 0x85 , doffset);
}
i->emit4(i->known[i->cmd.dest]);
} else {
i->emit( 0x89 );
if (doffset< 128 )
i->emit( 0x45 |(i->cmd.dest- 1 )<< 3 , ( char )doffset);
else
i->emit14( 0x85 |(i->cmd.dest- 1 )<< 3 , doffset);
}
break ;
case command::jz:
joffset = i->tgt->offset - (i->offset+ 2 ); // JMP SHORT
jshort = i->tgt->offset && (joffset>=- 128 ); //
if (!i->cmd.dest) { // JMP off
if (jshort)
i->emit( 0xeb , ( char )joffset);
else {
i->emit( 0xe9 );
if (i->tgt->offset) //
i->emit4(joffset- 3 );
else {
i->needfixat = code.size();
i->emit4( 0 );
}
}
break ;
}
if (jshort && (i->cmd.dest== 2 )) { // JECXZ
i->emit( 0xe3 , ( char )joffset);
break ;
}
// OR dst, dst / JZ off
i->emit( 0x0b , 0xc0 |(i->cmd.dest- 1 )<< 3 |(i->cmd.dest- 1 ));
if (jshort && (joffset>=- 126 ))
i->emit( 0x74 , ( char )(joffset- 2 ));
else {
i->emit( 0x0f , 0x84 );
if (i->tgt->offset) //
i->emit4(joffset- 6 );
else {
i->needfixat = code.size();
i->emit4( 0 );
}
}
break ;
case command::echo: // PUSH live / PUSH dst / CALL [EBP+?] / ADD ESP, 4 / POP live
foreach(rp, i->onexitp) if (*rp!= 4 ) i->emit( 0x50 |(*rp- 1 ));
if (!i->cmd.dest) { // imm, [EBP+8]
if (regsize== 4 ) i->emit( 0x68 ); else i->emit( 0xbf );
i->needfixat = code.size();
i->emit4( 0 );
i->emit( 0xff , 0x55 , 2 *regsize);
} else {
if (i->known.count(i->cmd.dest)) { // imm, [EBP+4]
imm = i->known[i->cmd.dest];
if (regsize== 8 )
i->emit14( 0xbf , imm);
else if ((imm>=- 128 )&&(imm< 128 ))
i->emit( 0x6a , ( char )imm);
else
i->emit14( 0x68 , imm);
} else if (regsize== 4 ) // dst, [EBP+4]
i->emit( 0x50 |(i->cmd.dest- 1 ));
else
i->emit( 0x8b , 0xf8 |(i->cmd.dest- 1 ));
i->emit( 0xff , 0x55 , regsize);
}
if (regsize== 4 ) i->emit( 0x83 , 0xc4 , 4 );
foreachr(rp, i->onexitp) if (*rp!= 4 ) i->emit( 0x58 |(*rp- 1 ));
break ;
case command::mov: // MOV dst, imm
if (i->cmd.imm)
i->emit14( 0xb8 |(i->cmd.dest- 1 ), i->cmd.imm);
else // XOR dst, dst
i->emit( 0x33 , 0xc0 |(i->cmd.dest- 1 )<< 3 |(i->cmd.dest- 1 ));
break ;
case command::load: // MOV dst, [EBP+(3+i)*4]
doffset = 0x18 +(i->cmd.imm- 1 )* 4 ;
i->emit( 0x8b );
if (doffset< 128 )
i->emit( 0x45 |(i->cmd.dest- 1 )<< 3 , ( char )doffset);
else
i->emit14( 0x85 |(i->cmd.dest- 1 )<< 3 , doffset);
break ;
case command::input: // PUSH live / CALL [EBP+0] / XCHG EAX, dst / POP live
foreach(rp, i->onenterp) if (*rp!= 4 ) i->emit( 0x50 |(*rp- 1 ));
i->emit( 0xff , 0x55 , 0 );
if (i->cmd.dest!= 1 ) i->emit( 0x90 |(i->cmd.dest- 1 ));
foreachr(rp, i->onenterp) if (*rp!= 4 ) i->emit( 0x58 |(*rp- 1 ));
break ;
case command::add:
// 1 ; src2
if (i->known.count(i->cmd.src1) || (i->cmd.src2==i->cmd.dest))
std::swap(i->cmd.src1, i->cmd.src2);
if (!i->cmd.src2) // MOV dst, src1
i->emit( 0x8b , 0xc0 |(i->cmd.dest- 1 )<< 3 |(i->cmd.src1- 1 ));
else if (i->known.count(i->cmd.src2)) {
imm = i->known[i->cmd.src2];
addimm :
if (i->cmd.dest==i->cmd.src1) // ADD dst, imm
if (imm== 1 ) // INC dst
i->emit( 0xff , 0xc0 |(i->cmd.dest- 1 ));
else if (imm==- 1 ) // DEC dst
i->emit( 0xff , 0xc8 |(i->cmd.dest- 1 ));
else if ((imm>=- 128 )&&(imm< 128 ))
i->emit( 0x83 , 0xc0 |(i->cmd.dest- 1 ), ( char )imm);
else // for imm=128 we might use SUB Edst, -128
i->emit114( 0x81 , 0xc0 |(i->cmd.dest- 1 ), imm);
else // LEA dst, [src1+imm]
if ((imm>=- 128 )&&(imm< 128 ))
i->emit( 0x8d , 0x40 |(i->cmd.dest- 1 )<< 3 |(i->cmd.src1- 1 ), ( char )imm);
else
i->emit114( 0x8d , 0x80 |(i->cmd.dest- 1 )<< 3 |(i->cmd.src1- 1 ), imm);
} else if (i->cmd.dest==i->cmd.src1) // ADD dst, src2
i->emit( 0x03 , 0xc0 |(i->cmd.dest- 1 )<< 3 |(i->cmd.src2- 1 ));
else // LEA dst, [src1+src2]
i->emit( 0x8d , (i->cmd.dest- 1 )<< 3 | 4 , (i->cmd.src1- 1 )<< 3 |(i->cmd.src2- 1 ));
break ;
case command::sub: // ...
case command::mul: // ...
case command::div:
if (i->known.count(i->cmd.src2) && i->known[i->cmd.src2]== 2 ) {
if (i->cmd.dest!=i->cmd.src1) // MOV dst, src1 / SAR dst, 1
i->emit( 0x8b , 0xc0 |(i->cmd.dest- 1 )<< 3 |(i->cmd.src1- 1 ));
i->emit( 0xd1 , 0xf8 |(i->cmd.dest- 1 ));
break ;
}
// EAX,EDX;
// EDI,ESI,
// MOV EDI, EAX / MOV EAX, src1 / MOV ESI, EDX / XOR EDX, EDX
// IDIV src2 / XCHG dst, EAX / MOV EDX, ESI / MOV EAX, EDI
saveAX = i->onexitp.count((physreg) 1 ) && (i->cmd.dest!= 1 );
saveDX = i->onexitp.count((physreg) 3 ) && (i->cmd.dest!= 3 );
if (saveAX || (i->cmd.src2== 1 )) i->emit( 0x8b , 0xf8 );
if (i->cmd.src1!= 1 )
if (i->known.count(i->cmd.src1))
i->emit14( 0xb8 , i->known[i->cmd.src1]);
else
i->emit( 0x8b , 0xc0 |(i->cmd.src1- 1 ));
if (saveDX || (i->cmd.src2== 3 )) i->emit( 0x8b , 0xf2 );
i->emit( 0x33 , 0xd2 );
if (i->cmd.src2== 1 ) // EDI
i->emit( 0xf7 , 0xff );
else if (i->cmd.src2== 3 ) // ESI
i->emit( 0xf7 , 0xfe );
else
i->emit( 0xf7 , 0xf8 |(i->cmd.src2- 1 ));
if (i->cmd.dest!= 1 )
i->emit( 0x90 |(i->cmd.dest- 1 ));
if (saveDX) i->emit( 0x8b , 0xd6 );
if (saveAX) i->emit( 0x8b , 0xc7 );
break ;
case command::eq: case command::ne: case command::ge:
case command::le: case command::gt: case command::lt:
// 1 ; src2
if (i->known.count(i->cmd.src1)) {
std::swap(i->cmd.src1, i->cmd.src2);
switch (i->cmd.opcode) {
case command::ge: i->cmd.opcode = command::le; break ;
case command::le: i->cmd.opcode = command::ge; break ;
case command::gt: i->cmd.opcode = command::lt; break ;
case command::lt: i->cmd.opcode = command::gt; break ;
}
}
// CMP src1, src2 / SETcc dstL / MOVZX dst, dstL
if (i->known.count(i->cmd.src2)) {
imm = i->known[i->cmd.src2];
if ((imm>=- 128 )&&(imm< 128 ))
i->emit( 0x83 , 0xf8 |(i->cmd.src1- 1 ), ( char )imm);
else if (i->cmd.src1== 1 )
i->emit14( 0x3d , imm);
else
i->emit114( 0x81 , 0xf8 |(i->cmd.src1- 1 ), imm);
} else
i->emit( 0x3b , 0xc0 |(i->cmd.src1- 1 )<< 3 |(i->cmd.src2- 1 ));
i->emit( 0x0f );
switch (i->cmd.opcode) {
case command::eq: i->emit( 0x94 ); break ;
case command::ne: i->emit( 0x95 ); break ;
// ...
}
i->emit( 0xc0 |(i->cmd.dest- 1 ));
i->emit( 0x0f , 0xb6 , 0xc0 |(i->cmd.dest- 1 )<< 3 |(i->cmd.dest- 1 ));
break ;
default :
yyerror( "unknown opcode" );
}
}
// --
int offset = code.size();
std::vector< int > offsets(strings.size()+ 1 );
foreach(i, strings) {
offsets[i->second] = offset;
offset += i->first.length();
offset++;
}
// :
foreach(i, pcode) if (i->needfixat)
if (i->cmd.opcode==command::jz)
i->fix4(i->tgt->offset-(i->offset+i->length));
else if (i->cmd.opcode==command::echo)
i->fix4(offsets[i->cmd.imm]);
write( 1 , &*code.begin(), code.size()); //
foreach(i, strings) //
write( 1 , i->first.c_str(), i->first.length()+ 1 );
[tyomitch@home ~]$ bison jsk.y
[tyomitch@home ~]$ c++ jsk.tab.c lex.yy.c -o jskc
[tyomitch@home ~]$ ./jskc < test.jsk > code
[tyomitch@home ~]$ cc jskld.c -o jskld
[tyomitch@home ~]$ ./jskld code
0 1000,
500? (1=, 2=, 3=) 1
249? (1=, 2=, 3=) 3
! !
00: 55 push rbp 01: 48 8b ef mov rbp, rdi 04: 53 push rbx 05: 33 c0 xor eax, eax 07: b9 e8 03 00 00 mov ecx, 0x3e8 0c: 50 push rax 0d: 51 push rcx 0e: bf 80 01 00 00 mov edi, 0x180 13: ff 55 10 call qword ptr [rbp + 0x10] 16: 59 pop rcx 17: 58 pop rax 18:50 push rax 19: 51 push rcx 1a: bf 00 00 00 00 mov edi, 0x0 1f: ff 55 08 call qword ptr [rbp + 0x8] 22: 59 pop rcx 23: 58 pop rax 24:50 push rax 25: 51 push rcx 26: bf fa 00 00 00 mov edi, 0xfa 2b: ff 55 10 call qword ptr [rbp + 0x10] 2e: 59 pop rcx 2f: 58 pop rax 30:50 push rax 31: 51 push rcx 32: bf e8 03 00 00 mov edi, 0x3e8 37: ff 55 08 call qword ptr [rbp + 0x8] 3a: 59 pop rcx 3b: 58 pop rax 3c: 50 push rax 3d: 51 push rcx 3e: bf 01 01 00 00 mov edi, 0x101 43: ff 55 10 call qword ptr [rbp + 0x10] 46: 59 pop rcx 47: 58 pop rax 48: 3b c1 cmp eax, ecx 4a: 0f 9e c2 setle dl 4d: 0f b6 d2 movzx edx, dl 50: 0b d2 or edx, edx 52: 0f 84 97 00 00 00 je 0xef 58: 8d 14 01 lea edx, [rcx + rax * 1] 5b: d1 fa sar edx, 1 5d: 50 push rax 5e: 51 push rcx 5f: 52 push rdx 60: bf bc 01 00 00 mov edi, 0x1bc 65: ff 55 10 call qword ptr [rbp + 0x10] 68: 5a pop rdx 69: 59 pop rcx 6a: 58 pop rax 6b: 50 push rax 6c: 51 push rcx 6d: 52 push rdx 6e: 8b fa mov edi, edx 70: ff 55 08 call qword ptr [rbp + 0x8] 73: 5a pop rdx 74: 59 pop rcx 75: 58 pop rax 76: 50 push rax 77: 51 push rcx 78: 52 push rdx 79: bf 26 01 00 00 mov edi, 0x126 7e: ff 55 10 call qword ptr [rbp + 0x10] 81: 5a pop rdx 82: 59 pop rcx 83: 58 pop rax 84: 50 push rax 85: 51 push rcx 86: 52 push rdx 87: ff 55 00 call qword ptr [rbp + 0x0] 8a: 93 xchg ebx, eax 8b: 5a pop rdx 8c: 59 pop rcx 8d: 58 pop rax 8e: 89 45 18 mov dword ptr [rbp + 0x18], eax 91: 83 fb 01 cmp ebx, 0x1 94: 0f 94 c0 sete al 97: 0f b6 c0 movzx eax, al 9a: 0b c0 or eax, eax 9c: 0f 84 08 00 00 00 je 0xaa a2: 8b 45 18 mov eax, dword ptr [rbp + 0x18] a5: 8d 4a ff lea ecx, [rdx-0x1] a8: eb 9e jmp 0x48 aa: 83 fb 02 cmp ebx, 0x2 ad: 0f 94 c0 sete al b0: 0f b6 c0 movzx eax, al b3: 0b c0 or eax, eax b5: 0f 84 04 00 00 00 je 0xbf bb: 03 c2 add eax, edx bd: eb 89 jmp 0x48 bf: 8b 45 18 mov eax, dword ptr [rbp + 0x18] c2: 83 fb 03 cmp ebx, 0x3 c5: 0f 94 c2 sete dl c8: 0f b6 d2 movzx edx, dl cb: 0b d2 or edx, edx cd: 0f 84 0b 00 00 00 je 0xde d3: bf a0 01 00 00 mov edi, 0x1a0 d8: ff 55 10 call qword ptr [rbp + 0x10] db: 5b pop rbx dc: 5d pop rbp dd: c3 ret de: 50 push rax df: 51 push rcx e0: bf c4 01 00 00 mov edi, 0x1c4 e5: ff 55 10 call qword ptr [rbp + 0x10] e8: 59 pop rcx e9: 58 pop rax ea: e9 59 ff ff ff jmp 0x48 ef: bf 59 01 00 00 mov edi, 0x159 f4: ff 55 10 call qword ptr [rbp + 0x10] f7: 5b pop rbx f8: 5d pop rbp f9: c3 ret
POP reg
, and the next begins in PUSH reg
, both of these instructions can be deleted;SETcc regl / MOVZX reg, regl / OR reg, reg / JZ offset
combination can be replaced with one JNcc offset
instruction if reg
not alive at the JZ
output.Source: https://habr.com/ru/post/103402/
All Articles