FreeBSD/i386 な環境で perl のコードを実行する方法

今日からはじめての FreeBSD プログラミング、ということで、
まず最初に execve() を使って Hello, world! するプログラムを書きました。

 8048080:       68 2d 65 00 00          push   $0x652d
 8048085:       89 e6                   mov    %esp,%esi
 8048087:       68 6c 00 00 00          push   $0x6c
 804808c:       68 2f 70 65 72          push   $0x7265702f
 8048091:       68 2f 62 69 6e          push   $0x6e69622f
 8048096:       68 2f 75 73 72          push   $0x7273752f
 804809b:       89 e7                   mov    %esp,%edi
 804809d:       68 5c 6e 22 00          push   $0x226e5c
 80480a2:       68 72 6c 64 21          push   $0x21646c72
 80480a7:       68 2c 20 77 6f          push   $0x6f77202c
 80480ac:       68 65 6c 6c 6f          push   $0x6f6c6c65
 80480b1:       68 74 20 22 48          push   $0x48222074
 80480b6:       68 70 72 69 6e          push   $0x6e697270
 80480bb:       89 e2                   mov    %esp,%edx
 80480bd:       31 c0                   xor    %eax,%eax
 80480bf:       50                      push   %eax
 80480c0:       52                      push   %edx
 80480c1:       56                      push   %esi
 80480c2:       57                      push   %edi
 80480c3:       89 e1                   mov    %esp,%ecx
 80480c5:       50                      push   %eax
 80480c6:       51                      push   %ecx
 80480c7:       57                      push   %edi
 80480c8:       b0 3b                   mov    $0x3b,%al
 80480ca:       50                      push   %eax
 80480cb:       cd 80                   int    $0x80
 80480cd:       31 db                   xor    %ebx,%ebx
 80480cf:       53                      push   %ebx
 80480d0:       31 c0                   xor    %eax,%eax
 80480d2:       40                      inc    %eax
 80480d3:       50                      push   %eax
 80480d4:       cd 80                   int    $0x80

FreeBSD は syscall の呼び出し規約は Linux と違っていて、引数は全部スタック渡して int 80h するみたい。

FreeBSD Assembly Language Programming

kernel:
	int	80h	; Call kernel
	ret

open:
	push	dword mode
	push	dword flags
	push	dword path
	mov	eax, 5
	call	kernel
	add	esp, byte 12
	ret

call 命令を使いたくない場合は、余分に1回ダミーでpushすれば大丈夫。

open:
	push	dword mode
	push	dword flags
	push	dword path
	mov	eax, 5
	push	eax		; Or any other dword
	int	80h
	add	esp, byte 16

これに対して Linux は引数の一部にレジスタを使う(EBX, ECX, EDX …)。EAX の使い方はほぼ同じ。

open:
	mov	eax, 5
	mov	ebx, path
	mov	ecx, flags
	mov	edx, mode
	int	80h

FreeBSD で定義されている syscall 番号を EAX レジスタに入れて、int 80h すればシステムコールが実行されます。

$ cat /usr/src/sys/kern/syscalls.master

 $FreeBSD: src/sys/kern/syscalls.master,v 1.198.2.5 2006/10/10 13:19:47 rwatson Exp $
;       from: @(#)syscalls.master       8.2 (Berkeley) 1/13/94
;
; System call name/number master file.
; Processed to created init_sysent.c, syscalls.c and syscall.h.

・・・(略)・・・
0       AUE_NULL        MSTD    { int nosys(void); } syscall nosys_args int
1       AUE_EXIT        MSTD    { void sys_exit(int rval); } exit \
                                    sys_exit_args void
2       AUE_FORK        MSTD    { int fork(void); }
3       AUE_NULL        MSTD    { ssize_t read(int fd, void *buf, \
                                    size_t nbyte); }
4       AUE_NULL        MSTD    { ssize_t write(int fd, const void *buf, \
                                    size_t nbyte); }
・・・(略)・・・
59      AUE_EXECVE      MSTD    { int execve(char *fname, char **argv, \
                                    char **envv); }
・・・(略)・・・

とりあえず、勢いで作ってみた NASM のソース貼っておきます

NASM

section .text
global _start

_start:
  push 0x00652d
  mov  esi, esp

  push 0x006c
  push 0x7265702f
  push 0x6e69622f
  push 0x7273752f
  mov  edi, esp

  push 0x00226e5c
  push 0x21646c72
  push 0x6f77202c
  push 0x6f6c6c65
  push 0x48222074
  push 0x6e697270
  mov  edx, esp

  xor eax, eax
  push eax
  push edx
  push esi
  push edi

  mov ecx, esp

  push eax
  push ecx
  push edi
  mov  al, 59
  push eax
  int 0x80

  xor  ebx, ebx
  push ebx
  xor  eax, eax
  inc  eax
  push eax
  int 0x80

■ 実行結果

$ nasm -f elf e.asm && ld -s -o e e.o && ./e && echo ok
Hello, world!
ok
$

FreeBSD の標準の as コマンドは gas みたいなのですが、どうも私は GAS の表記に慣れていなくて、ついつい NASM を使ってしまいます。。。

参考文献: Linux のアセンブラー: GAS と NASM を比較する


perl から任意の C ライブラリを呼び出す方法 - kazuhoのメモ置き場

id:kazuhooku さんの手法を応用すると、以下のように感じで Perl のプログラムから perl のコードを実行することが出来ます。

#!/usr/bin/perl
use DynaLoader;
my $s="h\x2de\x00\x00\x89\xe6hl\x00\x00\x00".
"h\x2fperh\x2fbinh\x2fusr\x89\xe7".
"h\x5cn\x22\x00hrld\x21h\x2c\x20wohelloht\x20\x22Hhprin".
"\x89\xe21\xc0PRVW\x89\xe1PQW\xb0\x3bP\xcd\x80".
"1\xdbS1\xc0\x40P\xcd\x80";
DynaLoader::dl_install_xsub("X",unpack("L",pack("P".length($s),$s)));X();

ここでは print "Hello, world!\n" の文字列が4byte毎にpush命令に分割されています。自分でコードをpushする際は、文字列の終端の \0 も忘れないように push しましょう。

FreeBSD FreeBSD 6.3-RELEASE #0: Wed Jan 16 04:18:52 UTC 2008 i386 で実行した結果は以下になりました。

Hello, world!

kazuhooku++

とりあえず、今週はここまで。