جمع با jit
۱۳۹۷ فروردین ۱۴, سهشنبه ساعت ۲۰:۱۶آموزش خوبی برای assembly هست.
https://github.com/pejman-hkh/jit
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
int exec( unsigned char *code, int size ) {
void *mem = mmap(NULL, size, PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
memcpy(mem, code, size);
int (*func)() = mem;
return func();
}
int main(int argc, char *argv[]) {
// Machine code for:
if (argc < 3) {
fprintf(stderr, "Usage: ./a.out <integer> <integer>\n");
return 1;
}
// mov eax, 0
// mov ebx, 0
// add eax, ebx
// ret
//x86 sum
unsigned char code[] = {
0xB8, atoi(argv[1]), 0x00, 0x00, 0x00,
0xBB, atoi(argv[2]), 0x00, 0x00, 0x00,
0x01, 0xD8,
0xC3
};
printf("%d\n", exec( code, sizeof( code ) ) );
}
https://defuse.ca/online-x86-assembler.htm#disassembly
سایت خوبی هست برای تبدیل کدهای اسمبلی به دستورات cpu intel x86
یک مثال خیلی ساده برای جمع دو عدد.
فرض کنید که بخواهیم عملیات ریاضی زیر را انجام دهیم :
5+2*12/4+3
کد اسمبلی این عملیات به شکل زیر خواهد بود :
mov eax, 12
mov edx, 0
mov ebx, 4
div ebx
mov ebx, 2
mul ebx
mov ebx, 5
add eax, ebx
mov ebx, 3
add eax, ebx
ret
که در صورتی که با تابع exec آنرا اجرا کنیم کد به شکل زیر خواهد بود و جواب ۱۴ خواهد شد :
unsigned char code[] = {
0xB8, 0x0C, 0x00, 0x00, 0x00,
0xBA, 0x00, 0x00, 0x00, 0x00,
0xBB, 0x04, 0x00, 0x00, 0x00,
0xF7, 0xF3,
0xBB, 0x02, 0x00, 0x00, 0x00,
0xF7, 0xE3,
0xBB, 0x05, 0x00, 0x00, 0x00,
0x01, 0xD8,
0xBB, 0x03, 0x00, 0x00, 0x00,
0x01, 0xD8,
0xC3
};
printf("%d\n", exec( code, sizeof( code ) ) );
خواندن و تبدیل کردن کد بالا به کد اسمبلی و اسمبل کردن کد کاری هست که من میخواهم آنرا انجام دهم
یک جمع پیشرفته تر را در نظر بگیرید. اینجا میخواهم از stack استفاده کنم :
4
+
2*3*5
+
6*7
+
4*1
کد اسمبلی این جمع به شکل زیر خواهد بود :
mov eax, 2
mov ebx, 3
mul ebx
mov ebx, 5
mul ebx
push eax
mov eax, 6
mov ebx, 7
mul ebx
push eax
mov eax, 4
mov ebx, 1
mul ebx
push eax
pop eax
pop ebx
add eax,ebx
pop ebx
add eax, ebx
mov ebx,4
add eax, ebx
هر بار که ضرب و تقسیم انجام میدهم مقدار eax را در stack push میکنم و برای جمع کردن همه ی مقدارها را به ترتیب pop میکنم و جمع و یا تفریق را انجام میدهم.
بایت کد، کد اسمبلی بالا به شکل زیر خواهد بود :
0: b8 02 00 00 00 mov eax,0x2
5: bb 03 00 00 00 mov ebx,0x3
a: f7 e3 mul ebx
c: bb 05 00 00 00 mov ebx,0x5
11: f7 e3 mul ebx
13: 50 push eax
14: b8 06 00 00 00 mov eax,0x6
19: bb 07 00 00 00 mov ebx,0x7
1e: f7 e3 mul ebx
20: 50 push eax
21: b8 04 00 00 00 mov eax,0x4
26: bb 01 00 00 00 mov ebx,0x1
2b: f7 e3 mul ebx
2d: 50 push eax
2e: 58 pop eax
2f: 5b pop ebx
30: 01 d8 add eax,ebx
32: 5b pop ebx
33: 01 d8 add eax,ebx
35: bb 04 00 00 00 mov ebx,0x4
3a: 01 d8 add eax,ebx
3c: c3 ret
جواب معادله بالا مقدار ۸۰ خواهد بود. برای اینکار میتوانید کد زیر را اجرا کنید :
unsigned char code[] = {
0xB8, 0x02, 0x00, 0x00, 0x00, 0xBB, 0x03, 0x00, 0x00, 0x00, 0xF7, 0xE3, 0xBB, 0x05, 0x00, 0x00, 0x00, 0xF7, 0xE3, 0x50, 0xB8, 0x06, 0x00, 0x00, 0x00, 0xBB, 0x07, 0x00, 0x00, 0x00, 0xF7, 0xE3, 0x50, 0xB8, 0x04, 0x00, 0x00, 0x00, 0xBB, 0x01, 0x00, 0x00, 0x00, 0xF7, 0xE3, 0x50, 0x58, 0x5B, 0x01, 0xD8, 0x5B, 0x01, 0xD8, 0xBB, 0x04, 0x00, 0x00, 0x00, 0x01, 0xD8, 0xC3
};
printf("%d\n", exec( code, sizeof( code ) ) );
اگر قرار باشد که جمع بالا را انجام دهم یکبار bytecode آنرا ذخیره میکنم و دفعه بعد فقط آنرا روی رم مینویسم و خروجی آنرا میگیرم. این دقیقا همان کاری است که زبان های jit انجام میدهند. زبانی مثل جاوا کد شما را به bytecode خودش تبدیل میکند و دفعه بعد فقط آنرا به bytecode مورد نظر cpu که مثلا x86 و یا x64 هست تبدیل میکند و آنرا روی رم مینویسد و اجرا میکند. پیشتر که رفتیم سعی میکنم که یک متغییر تعریف کنم و به آن مقدار دهم تا کمی پیشرفته تر کار کنیم.
اگر بخواهیم کد بالا را به کد c تبدیل کنیم به شکل زیر در خواهد آمد :
mov( EAX, 2 );
mov( EBX, 3 );
mul( EBX );
mov( EBX, 5 );
mul( EBX );
push( EAX );
mov( EAX, 6 );
mov( EBX, 7 );
mul( EBX );
push( EAX );
mov( EAX, 4 );
mov( EBX, 1 );
mul( EBX );
push( EAX );
pop( EAX );
pop( EBX );
add( EAX, EBX );
pop( EBX );
add( EAX, EBX );
mov( EBX, 4 );
add( EAX, EBX );
ret();
خروجی این کد باید bytecode بالا باشد.
برای اجرای کد بالا من چند تا تابع کوچیک ناقص نوشتم که کد مارو میسازه و باز خروجی ما جمعش ۸۰ خواهد شد :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
int exec( unsigned char *code, int size ) {
void *mem = mmap(NULL, size, PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
memcpy(mem, code, size);
int (*func)() = mem;
return func();
}
enum {
EAX,
EBX,
ECX,
EDX,
};
unsigned char asm_instruction[1000];
int asm_iter = 0;
void set_asm( unsigned char * ret, int size ) {
for( int i = 0; i < size; i++ ) {
asm_instruction[asm_iter++] = ret[i];
}
}
void mov( int reg, int val ) {
unsigned char ret[5];
switch( reg ) {
case EAX :
ret[0] = 0xB8;
ret[1] = val;
ret[2] = 0x00;
ret[3] = 0x00;
ret[4] = 0x00;
break;
case EBX :
ret[0] = 0xBB;
ret[1] = val;
ret[2] = 0x00;
ret[3] = 0x00;
ret[4] = 0x00;
break;
case EDX :
ret[0] = 0xBA;
ret[1] = val;
ret[2] = 0x00;
ret[3] = 0x00;
ret[4] = 0x00;
break;
}
set_asm( ret, sizeof( ret ) );
}
void push( int reg ) {
unsigned char ret[1];
switch( reg ) {
case EAX :
ret[0] = 0x50;
break;
case EBX :
ret[0] = 0x53;
break;
}
set_asm( ret, sizeof( ret ) );
}
void add( int reg, int reg1 ) {
unsigned char ret[2];
switch( reg ) {
case EAX :
ret[0] = 0x01;
switch( reg1 ) {
case EBX :
ret[1] = 0xD8;
break;
}
break;
}
set_asm( ret, sizeof( ret ) );
}
void mul( int reg ) {
unsigned char ret[2];
switch( reg ) {
case EAX :
ret[0] = 0xF7;
ret[1] = 0xE0;
break;
case EBX :
ret[0] = 0xF7;
ret[1] = 0xE3;
break;
}
set_asm( ret, sizeof( ret ) );
}
void pop( int reg ) {
unsigned char ret[1];
switch( reg ) {
case EAX :
ret[0] = 0x58;
break;
case EBX :
ret[0] = 0x5B;
break;
}
set_asm( ret, sizeof( ret ) );
}
void ret() {
unsigned char ret[1];
ret[0] = 0xC3;
set_asm( ret, sizeof( ret ) );
}
int main(int argc, char *argv[]) {
mov( EAX, 2 );
mov( EBX, 3 );
mul( EBX );
mov( EBX, 5 );
mul( EBX );
push( EAX );
mov( EAX, 6 );
mov( EBX, 7 );
mul( EBX );
push( EAX );
mov( EAX, 4 );
mov( EBX, 1 );
mul( EBX );
push( EAX );
pop( EAX );
pop( EBX );
add( EAX, EBX );
pop( EBX );
add( EAX, EBX );
mov( EBX, 4 );
add( EAX, EBX );
ret();
/*for( int i = 0; i < asm_iter; i++ ) {
printf("%x ", asm_instruction[i] );
}*/
printf("%d\n", exec( asm_instruction, asm_iter ) );
return 0;
}
از کد بالا میشه به عنوان یک assembler خیلی خیلی ساده استفاده کرد و اون را حتی develope کرد برای نوشتن مابقی دستوراتمون.
https://github.com/pejman-hkh/tiny-jit-assembler
با استفاده از کد بالا میشه خیلی راحت حداقل یک معادله ساده ریاضی رو به روش jit انجام داد.
اول از همه باید کد رو parsing کرد و بعد ابتدا ضرب و تقسیم ها رو انجام داد و همه رو push کرد داخل stack و بعد یکی یکی اونا رو pop کرد و جمع و یا تفریق کنیم و در آخر اون رو ret کنیم.
روش نهایی برای جمع بالا
//push all in stack
mov ( EAX, 4);
push( EAX );
mov( EAX, 2 );
mov( EBX, 3 );
mul( EBX );
mov( EBX, 5 );
mul( EBX );
push( EAX );
mov( EAX, 6 );
mov( EBX, 7 );
mul( EBX );
push( EAX );
mov( EAX, 4 );
mov( EBX, 1 );
mul( EBX );
push( EAX );
//pop first number
pop( EAX );
//sum all numbers
pop( EBX );
add( EAX, EBX );
pop( EBX );
add( EAX, EBX );
pop( EBX );
add( EAX, EBX );
//ret result
ret();