読者です 読者をやめる 読者になる 読者になる

C言語から機械語を直接実行する方法

v8(JavaScript Engine)を読んでいると、機械語を直接生成してから、それを関数型に流し込んで実行していることが分かってきました。面白いと思ったのでC言語で簡単に再現してみました。

コード

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <assert.h>
int sum(int a, int b) { return a + b;}
int main(void) {
unsigned char* buf;
int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
  buf = mmap(NULL, 80, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  memset(buf, 0, 80);
  int i = 0;
  buf[i++] = 0x55;                               // push %ebp
  buf[i++] = 0x89; buf[i++] = 0xe5;              // mov %esp, %ebp
  buf[i++] = 0x8b; buf[i++] = 0x45; buf[i++] = 0x0c;   // mov 0xc(%ebp), %eax
  buf[i++] = 0x8b; buf[i++] = 0x55; buf[i++] = 0x08;   // mov 0x8(%ebp), %edx
  buf[i++] = 0x8d; buf[i++] = 0x04; buf[i++] = 0x02; // lea (%edx,%eax,1),%eax
  buf[i++] = 0x5d;                                // pop %ebp
  buf[i++] = 0xc3;                                // ret
  assert(!memcmp(buf, sum, i));// 関数sumとbufが等しいことを確認
int (*func)(int , int);
   func = (void*)buf;
  assert(3 == (*func)(1, 2) && "1+2=3");
  assert(100 == (*func)(99, 1) && "99+1=100");
  assert(8 == (*func)(3, 5) && "3+5=8");
  assert(5 == (*func)(3, 2) && "3+2=5");
  printf("all tests passed.\n");
  munmap(buf, 80);
  return 0;
}

機械コードの調べ方


機械コードはcpuに依存していますが、ぼくはgccとobjdumpに教えてもらうことにしました。まず次のコードをンパイル(gcc )して実行します。

#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <assert.h>
int sum(int a, int b) { return a + b;}
int main(void) {
unsigned char* buf;
int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
  buf = mmap(NULL, 80, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  memset(buf, 0, 80);
  memcpy(buf, sum, 80);
  int (*func)(int, int);
  func = (void*)buf;
  assert(3 == (*func)(1,2) && "1 + 2 = 3");
  int i = 0;
  while(i < 80)
    printf("%x\n", buf[i++]);
  munmap(buf, 80);
  return 0;
}

結果として"55\n89\ne5\n8b\n45\nc\n8b\n55\n8\n8d..."が出力されます。このコードは関数sumのエントリーポイントからのコードを16進数で表したものです。sumの最後のところを調べるためにobjdump -d a.out して、sumがc3のところで終わることを確認します。これで関数sumの機械コードが分かりました。(objdumpの出力の意味が分かる場合はobjdump -d a.outではじめから関数sumのコードを調べれば良いです)