جمع با jit


https://0xax.github.io/

آموزش خوبی برای 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();
دیدگاه بگذارید

دیدگاه

دیدگاه ها بدون تایید میباشد.