loader

まだ雪が降る。雪が止むと散歩に出るんだけど、農道についたわだち。その間は処女雪。気持ちよく散歩の足跡を残す。

振り返ってみたら、随分とがに股歩きになってるな。これは矯正しなければ。。。出来れば、モデル歩きぐらいしたいぞ。(って、どういう歩きなんだろう)

モナコ在住のあの人に聞いてみればいいのかな? それより

正しい歩行とは

【正しい歩き方・後ろ歩き体操と土踏まずのアーチを作る体操 】

正しい歩き方について 5つのポイントを紹介します

かかと着地はダメなんか。陸王の足袋靴の話を思い出せ。それと、大股歩きだな。

boot loader

前回からやってるxv6の勉強。boot loaderは分かった積りになってた。補強のために、下記も 見ている。

Linuxがブートするまで

OSの起動に必要な「ブートローダー」を自作してみよう (3/3)

Linux Insides : カーネル起動プロセス part1

GRUBその1 ブートローダーとGRUB

でも、まあ少しは手を動かせよって事で、loader部分だけを抜き出して、どんな風にデータが メモリーに配置されるか表示するようにしてみた。ええ、bochsなりでアセンブルコードを 追いかけるって手はありますけどね。出来たら避けたい糞石コードです。

// Boot loader.
typedef unsigned int   uint;
typedef unsigned short ushort;
typedef unsigned char  uchar;

#include "elf.h"
#include <stdio.h>

void stosb(void *addr, int data, int cnt){  // padding data at addr count cnt
  printf("  adr=%x cnt=%x\n", addr, cnt);
}

void bootmain(FILE* fd) {
  struct elfhdr *elf;
  struct proghdr *ph, *eph;
  uchar* pa;
  uchar  buf[4096];

  elf = (struct elfhdr*)buf;

  //  readseg((uchar*)elf, 4096, 0);
  fread((uchar*)elf, 1, 4096, fd);

  // Is this an ELF executable?
  if(elf->magic != ELF_MAGIC)
    return;  // let bootasm.S handle error

  // Load each program segment (ignores ph flags).
  ph = (struct proghdr*)((uchar*)elf + elf->phoff);
  eph = ph + elf->phnum;
  for(; ph < eph; ph++){
    pa = (uchar*)ph->paddr;
    //  readseg(pa, ph->filesz, ph->off);
    printf("adr=%x size=%x offset=%x\n", pa, ph->filesz, ph->off);
    if(ph->memsz > ph->filesz)
      stosb(pa + ph->filesz, 0, ph->memsz - ph->filesz);
  }
  printf("start=%x\n", elf->entry);
}

int main(){
   FILE* fd;

   fd = fopen("xv6.img", "rb");
   fseek(fd, 512, SEEK_SET);     // skip MBR
   bootmain(fd);
   fclose(fd);
}

bootmain.cを土台にちょっとDISKに読みに行くコードは、読んだ積りでprintfにしました。 stobsって糞石のアセンブルになってるんですけど、比較的よく使われる文字移動用スペシャル命令なのかな。CISC丸出しだと思うぞ。このstobsでZEROクリアしてるエリアって、BSS領域なんだろうね。

新たに追加したのはmain()。特徴的なのは、seekしてMBR相当をすっ飛ばすようにしてる事 ぐらいかなあ。それとelf.hは既存のやつを流用。教育用コードって事なのに、何もコメントが 入っていない。

しょうがないので、OpenBSDから、/sys/sys/exec_elf.hを引っ張ってきて載せておくか。

/* ELF Header */
typedef struct elfhdr {
        unsigned char   e_ident[EI_NIDENT]; /* ELF Identification */
        Elf32_Half      e_type;         /* object file type */
        Elf32_Half      e_machine;      /* machine */
        Elf32_Word      e_version;      /* object file version */
        Elf32_Addr      e_entry;        /* virtual entry point */
        Elf32_Off       e_phoff;        /* program header table offset */
        Elf32_Off       e_shoff;        /* section header table offset */
        Elf32_Word      e_flags;        /* processor-specific flags */
        Elf32_Half      e_ehsize;       /* ELF header size */
        Elf32_Half      e_phentsize;    /* program header entry size */
        Elf32_Half      e_phnum;        /* number of program header entries */
        Elf32_Half      e_shentsize;    /* section header entry size */
        Elf32_Half      e_shnum;        /* number of section header entries */
        Elf32_Half      e_shstrndx;     /* section header table's "section
                                           header string table" entry offset */
} Elf32_Ehdr;

/* Program Header */
typedef struct {
        Elf32_Word      p_type;         /* segment type */
        Elf32_Off       p_offset;       /* segment offset */
        Elf32_Addr      p_vaddr;        /* virtual address of segment */
        Elf32_Addr      p_paddr;        /* physical address - ignored? */
        Elf32_Word      p_filesz;       /* number of bytes in file for seg. */
        Elf32_Word      p_memsz;        /* number of bytes in mem. for seg. */
        Elf32_Word      p_flags;        /* flags */
        Elf32_Word      p_align;        /* memory alignment */
} Elf32_Phdr;

64Bit用も有るけど、取り合えず32Bit用の方ね。

講釈はこれぐらいにして、動かしてみる。まずはちゃんと動くdebianのやつ。

$ ./a.out
adr=100000 size=a516 offset=1000
  adr=10a516 cnt=af92
adr=0 size=0 offset=0
start=10000c

こちらは、openbsdのやつ。

$ ./a.out
adr=0 size=e0 offset=34
adr=801080c8 size=13 offset=90c8
adr=100000 size=7769 offset=1000
adr=80107780 size=4060 offset=8780
  adr=8010b7e0 cnt=af88
adr=80116768 size=9f0 offset=d768
adr=8010b610 size=80 offset=c610
adr=0 size=0 offset=0
start=10000c

明らかに違うねぇ。正解をreadelfを使って確かめてみる。xv6.img = MBR + kernel って図式になってるんで、kernelなら素直にreadelfにかけられる。

$ readelf -l debian/kernel

Elf file type is EXEC (Executable file)
Entry point 0x10000c
There are 2 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0x80100000 0x00100000 0x0a516 0x154a8 RWE 0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .stab .stabstr .data .bss
   01
$ readelf -l openbsd/kernel

Elf file type is DYN (Shared object file)
Entry point 0x10000c
There are 7 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00001034 0x00000000 0x000e0 0x000e0 R E 0x4
  INTERP         0x0090c8 0x801080c8 0x801080c8 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /usr/lib/libc.so.1]
  LOAD           0x001000 0x80100000 0x00100000 0x07769 0x07769 R E 0x1000
  LOAD           0x008780 0x80107780 0x80107780 0x04060 0x0efe8 RW  0x1000
  LOAD           0x00d768 0x80116768 0x80116768 0x009f0 0x009f0 R   0x1000
  DYNAMIC        0x00c610 0x8010b610 0x8010b610 0x00080 0x00080 RW  0x4
  NULL           0x000000 0x00000000 0x00000000 0x00000 0x00000     0

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .text
   03     .rodata .interp .dynsym .dynstr .hash .stab .stabstr .data .dynamic .got .got.plt .data.rel.ro.local .data.rel.ro .data.rel .bss
   04     .rel.dyn
   05     .dynamic
   06

Index02にtextが有って、Index03にdataが配置されてたな。そしてそのアドレスは、80107780って、とんでもない所になってる。これじゃ初期PDEが読めないわな。こんな配置になるのは、OpenBSD特有の仕様かしらん?

汎用化

パクッて来たコードをちょいと変更して、汎用化してみる。コマンドラインから、ファイル名を 受け付けるようにしただけだれど。readelfのプログラム・セグメントを表示出来るものだな。

int main(int argc, char *argv[]){
   FILE* fd;

   fd = fopen(argv[1], "rb");
   bootmain(fd);
   fclose(fd);
}
deb9:debian$ ./a.out /bin/ls
start=5430

ばぐってるな。3秒考えて原因が分かった。elf.hが32Bit用になってるからだろう。それを64Bitのアプリに適用しようとしても無理が有るんだな。ならば、逆に32Bitの環境なら動くんじゃねぇ。32Bit時代のウブが有るんで、そこで試してみる。

sakae@ub:/tmp$ ./a.out /bin/ls
adr=8048034 size=120 offset=34
adr=8048154 size=13 offset=154
adr=8048000 size=1e40c offset=0
adr=8067f00 size=444 offset=1ef00
  adr=8068344 cnt=c34
adr=8067f0c size=f0 offset=1ef0c
adr=8048168 size=44 offset=168
adr=8060b74 size=814 offset=18b74
adr=0 size=0 offset=0
adr=8067f00 size=100 offset=1ef00
start=804bee9
sakae@ub:/tmp$ readelf -l /bin/ls

Elf file type is EXEC (Executable file)
Entry point 0x804bee9
There are 9 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x1e40c 0x1e40c R E 0x1000
  LOAD           0x01ef00 0x08067f00 0x08067f00 0x00444 0x01078 RW  0x1000
  DYNAMIC        0x01ef0c 0x08067f0c 0x08067f0c 0x000f0 0x000f0 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x00044 0x00044 R   0x4
  GNU_EH_FRAME   0x018b74 0x08060b74 0x08060b74 0x00814 0x00814 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x01ef00 0x08067f00 0x08067f00 0x00100 0x00100 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag .note.gnu.build-id
   06     .eh_frame_hdr
   07
   08     .init_array .fini_array .jcr .dynamic .got

もう一つの loader

上でみたのは、カーネルをメモリーに配置するためのローダーだ。OSと言うかカーネルが動き出すと、今度は色々なアプリ(大きい所では、firefoxとか、小さいやつだとgrepとか)を呼び出す事になる。 その機能はカーネルが持っている。

シェルを通じてgrepしたり。そのためにはgrepのアプリと言うかコマンドを、メモリーに載せる 事が必要。勿論、システムコールを通じてね。

exec.cがそれを担当する。

  // Check ELF header
  if(readi(ip, (char*)&elf, 0, sizeof(elf)) != sizeof(elf))
    goto bad;
  if(elf.magic != ELF_MAGIC)
    goto bad;
  :
  // Load program into memory.
  sz = 0;
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    if(readi(ip, (char*)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;
    if((sz = allocuvm(pgdir, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;
    if(ph.vaddr % PGSIZE != 0)
      goto bad;
    if(loaduvm(pgdir, (char*)ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;
  }

簡単そうなって事で、echoをやってみる。xv6上でのバイナリーは、_echo なんで、まずは素性の調査から。

deb9:debian$ ./a.out _echo
adr=0 size=9d4 offset=80
  adr=9d4 cnt=c
adr=0 size=0 offset=0
start=0
deb9:debian$ readelf -l _echo

Elf file type is EXEC (Executable file)
Entry point 0x0
There are 2 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000080 0x00000000 0x00000000 0x009d4 0x009e0 RWE 0x10
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .eh_frame .bss
   01
deb9:debian$ ls -l _echo
-rwxr-xr-x 1 sakae sakae 12528 Feb 20 15:31 _echo
deb9:debian$ size _echo
   text    data     bss     dec     hex filename
   2511       0      12    2523     9db _echo

簡単なんで、グローバル変数は極少ない。

適当にBPを貼って、走らせてみる。

(gdb) bt
#0  allocuvm (pgdir=0x8deeb000, oldsz=0, newsz=2528) at vm.c:223
#1  0x80100af9 in exec (path=0x1840 "echo", argv=0x8dfc6ed0) at exec.c:52
#2  0x80105380 in sys_exec () at sysfile.c:420
#3  0x80104837 in syscall () at syscall.c:139
#4  0x801058b9 in trap (tf=0x8dfc6fb4) at trap.c:43
#5  0x8010561f in alltraps () at trapasm.S:20
#6  0x8dfc6fb4 in ?? ()

メモリー頂戴のおねだり。boot loaderの時は、そんなおねだりもなく(メモリーは十分に有るという前提ですから)、我が物顔にメモリーを使ってたけど、今度はそうはいかない。

(gdb) bt
#0  loaduvm (pgdir=0x8deeb000, addr=0x0, ip=0x80110b34 <icache+340>, offset=128, sz=2516) at vm.c:199
#1  0x80100b2f in exec (path=0x1840 "echo", argv=0x8dfc6ed0) at exec.c:56
#2  0x80105380 in sys_exec () at sysfile.c:420
:

メモリーが貰えたら、そこにバイナリーコードを埋め込むとな。簡略のチェックプログラムでは、バイナリーコードをポイントする変数としてファイルポインターだった。今回は、DISKから バイナリーがキャッシュに読み込まれているので、キャッシュ上のポインター ip が、その任を負っている。

本物のOSのやつ(loader)

最初にFreeBSDのカーネルを見たんだけど、ごちゃごちゃし過ぎです。ソースを見るならOpenBSDに限るって事で、/sys/kern/kern_exec.cあたりを見る。

execでは、いわゆるシェバングやバイナリーコードも実行する必要があったり、ロードの失敗に 備えて、巻き戻し(実行されなかった事にする)の準備も必要って事で、専用のエミュレーターが用意されてる。sys_execveの中。

        /* create the new process's VM space by running the vmcmds */
#ifdef DIAGNOSTIC
        if (pack.ep_vmcmds.evs_used == 0)
                panic("execve: no vmcmds");
#endif
        error = exec_process_vmcmds(p, &pack);

        /* if an error happened, deallocate and punt */
        if (error)
                goto exec_abort;

実行の主体は /sys/kern/exec_elf.cの中だ(バイナリーな場合)。この中に、elf_load_file関数がある。 この中で、ごちゃごちゃやってるのは分かるんだけど、候補が沢山あって絞り込めないな。

こういう時は実際に走らせて、追ってみるのが一番だろうね。 OpenBSD on QEMUあたりを思い出して、再び環境を作ってみるかな。

etc

機械学習の前に重要なデータ抽出・加工に便利なPythonライブラリ「pandas」の基本的な使い方のチュートリアル

translation of XV6