- 追加された行はこの色です。
- 削除された行はこの色です。
- xv6-read へ行く。
* ソフトウェアシステム論でxv6読み会をした時のメモ(2018/04/16) [#v9d0fb30]
** xv6のdebug † [#n9cfe161]
- qemu
- /mnt/dalmore-home/one/src/xv6-rpi/src
cd /mnt/dalmore-home/one/src/xv6-rpi/src
/net/open/Linux/arm/gcc-arm-none-eabi-7-2017-q4-major/bin/arm-none-eabi-gdb
kernel.elf
(gdb) b _start
(gdb) target remote :1234
qemu再起動
make clean;make -f makefile-armclang qemu-debug
CtrlA-Xで抜けれる
** Boot Sequence [#efd8496f]
***Boot [#t1a6269e]
Kernel load
Page Table(Boot用のpagetableの設定)
initialize(memory / IO / process / file)
ini process
- Kernel load
-- x86ならEFIがある
-- ARMだとVersatile(fimware)
- pagingを設定しなければ動かない
- Boot用のpage table の設定ができた後一連の初期化を行う
--memory
-- I/O
-- process
-- file
- 初期化が終わった後
--init process(process番号1番)
--init process は linux だと rc.d/~ の下にある
** ls 時の User と System [#ka6c601e]
User Sys
fork
exec ls (lsを探す)
opendir
open ls
(load ls (loadの後にmemory空間の初期化を行う)
page table reset
goto User Mode
opendir
system call (file IO) (VM)
(filesystemを読んでまたUserに値を返す)
- 10行目 VM の説明
--lsは自分で仮装メモリにアクセスしていくので memory allocation をやる
--malloc は User library だが memory の要求は System でやらなきゃいけない
--break とい System --call で memory を増やす
--memory を増やすと OS は最初に ls のバイナリを生成
--すると、break で取った領域が別にできる
--breakで 取った memory が全部 リアル memory に割り当てられるわけではない
--いくつかは仮想メモリに行く
--memory にアクセスした時に仮装メモリだったらtrapしてmemoryを割り当てる
--memoryが割り当てられなければ、他の実メモリを追い出して書き換える
--VM関係の一連の処理がある
-fork
--forkすると process structure ができる
--process自体は active や waitの状態を持っている
--複数のactiveがあると順に実行していく(scheduler)
-kernel の要素
--process management
--scheduler
--file IO
--Virtual Memory
この4つの Kernel の要素を読んでいく
** X.v6 の trace [#hb1fbcdd]
- arm 用の gdb で kernel.elf を開く
- dalmoreに入って
cd /mnt/dalmore-home/one/src/xv6-rpi/src
/net/open/Linux/arm/gcc-arm-none-eabi-7-2017-q4-major/bin/arm-none-eabi-gdb kernel .elf
qemuを立ち上げる 見たいのは Boot Sequence だから qemu-debug を使う
make clean; make -f makefile-armclang qemu-debug
***makefile-armclang の qemu-debug 部分 [#cfdfbc81]
qemu-debug : kernel.elf
@clear
@echo "Press Ctrl-A and then X to terminate QEMU session\n"
export QEMU_AUDIO_DRV=none ; $(QEMU) -M versatilepb -m 128 -cpu ${QEMUCPU
} -nographic -singlestep -d exec,cpu,guest_errors -D qemu.log -kernel kernel.elf -s -S
- -M versatilepb
--仮想メモリにこの firmwere を使うという指示
--versarilepb は Raspi(ARM) のfirmwereの一種
- -m 128
-- memory の量
- -cpu ${QEMUCPU}
-- cpu の種類
-- アーキテクチャによって命令違う
-- armのxv6を作る時のcompile時にcpuに教えるcpuの種類と合わせる必要がある
-- 名前がqemu側とcompile側のcpuの名前が違うので試行錯誤して合わせる
- -s -S
-- Boot時に debugger が接続するまで止めるようにする
** gdb
(gdb)b _start
# remote 接続
(gdb) target remote :1234
# bt: stackを参照して、stack上のfanction callの履歴を調べる
(gdb)bt
(gdb)info registarts (初期のレジスタ)
r0 ~ r12 (ARMのレジスタは0~12)
sp (stack pointer)
lr (link regester)
pc (program counter)
cpsr (cpu statusのレジスタ)
(gdb)disass
# disassemblerの略
(gdb)x/20x $pc+48+4
0x100030
# entry.Sのr1,=edata_entryやend_entryが$pc+48に変わってる # pcは次の命令を指している 10030->10038だから48からさらに+4
(gdb)p (void*) edata_entry
$1 = (void *0) 0x10588 <edata_entry>
# 10588がレジスタのr1に入る
(gdb)stepi
(gdb)info registers
r1 0x10588 66952
(gdb)stepi
(gdb)info registers
r1 0x10588 66952
r2 0x19000 102400
(gdb)define ni
>x/1i $pc
>x/1i $pc
>end
# こうするとentry.Sを1命令ずつ実行できる
(gdb)si
0x0001000c in _start ()
=> 0x1000c <_start+12>: cmp r1,r2
(gdb)si
0x0001000c in _start ()
=> 0x1014c <_start+16>: cmp r1,{r3}
(gdb)disass
=> 0x00010014 <*20>: blt
0x00010018 <*24>: msr
# blt でループしてることがわかる
(gdb)tb *0x00010018 (gdb)c
# bltのループを抜ける
(gdb)disass
(gbd)info registers
r1 0x19000 102400
r2 0x19000 102400
# r2の値になるまでr1をincrementすることでループを抜けてる事がわかる # 19000までmemoryがある
# CPUのコントロール
(gdb)si
(gdb)info registers
cpsr 0x600001d3
# entry.Sの MSRの行を実行 (gdb)si
(gdb)info registers cpsr 0xd3
# CPUのコントロールレジスタを指定している # supervisorじゃないとアクセスできない
# entry.Sの LDR の行を実行 (gdb) si
(gdb) info registers
sp 0x12000
# 0x0 -> 0x12000
# stack ponterが設定されたのでサブルーチンコールが使えるようになる
# サブルーチンコール 戻り先をstackに覚えておいてそこに飛ぶ
# lr(link register)に覚えさせる事で1回だけアクセスせずに飛ぶ事ができる # BL breach & link
(gdb)si
(gdb)info registers
lr 0x10024
(gdb)disass _start
0x00010024 <+36>: b
#startまできたのでここ以降はstart.c (gdb)l
# cなので next が使える
(gdb)next
(gdb)next
(gdb)s
set_bootpgtbl(... lent=1048576 ...)
(gdb)p (void*) len
$2 = (void *) 0x100000
# 1048576 は16進数で100000
- memory に直接 load できる値は 長い値だとmemory一旦置かないといけない
- 一番最初に呼び出すのは C で書かれたファイルじゃなくてアセンブラ
-- entry.S で行なっている
*** entry.S [#m1f4210a]
_start:
LDR r1, =edata_entry
LDR r2, =end_entry
MOV r3, #0x00
# BLT までをループ 1:
CMP r1, r2
STMLTIA r1!, {r3} # {}はレジスタのリスト r1にセーブする # !は セーブした数だけr1を進める(++とかと一緒)
# Memclear とかと一緒
BLT 1b
# initialize stack pointers for svc modes # CPUのコントロールレジスタを指定
# cpsr 0xd3
MSR CPSR_cxsf, #(SVC_MODE|NO_INT)
LDR sp, =svc_stktop
BL start
B.
- B . まで行くとそこを永遠とループする
-- ARM fault持ってるので Bではなくfaultにすべき
*** start.c [#nb867326]
166行目
void start (void)
{
uint32 vectbl;
_puts("starting xv6 for ARM...\n");
// double map the low memory, required to enable paging
// we do not map all the physical memory
set_bootpgtbl(0, 0, INIT_KERNMAP, 0);
set_bootpgtbl(KERNBASE, 0, INIT_KERNMAP, 0);
69行目
for (idx = 0; idx < len; idx++) {
pde = (phy << PDE_SHIFT);
if (!dev_mem) {
// normal memory, make it kernel-only, cachable, bufferable pde |= (AP_KO << 10)
|PE_CACHE | PE_BUF | KPDE_TYPE;
// |(or) を使って足して行く
} else {
// device memory, make it non-cachable and non-bufferable
pde |= (AP_KO << 10) | KPDE_TYPE;
}
- ARMv6 page table entry
-- https://developer.arm.com/docs/ddi0211/h/memory-management-unit/hardware-page-table-translation/armv6-page-table-translation-subpage-ap-bits-disabled
* ソフトウェアシステム論でxv6読み会をした時のメモ(2018/05/27) [#v9d0fb30]
** 読んだとこ [#le8b9f37]
- exec
- initcode
- swtch
- flush_tlb
-- userスペースにだけflush する(systemにはしない)
-fetchint
--kernel 側から指定されたプロセスのアドレスから integer を取ってくるサブルーチン
-trapret
--特権モードを抜ける
-coppyout
--current page table に乗ってたら memcopy でできるけど、乗ってないからcopyout必要
** exec [#k515474c]
- xv6-rpi/src から grep で exec を探す
-- exec.c
-- sysecxec の2つがある
sysファイルの中で exec 呼んでるので exec から読む
- gdb側
$ /net/open/Linux/arm/gcc-arm-none-eabi-7-2017-q4-major/bin/arm-none-eabi-gdb kernel.elf
(gdb) b _start
(gdb) target remote :1234
(gdb) l exec
(gdb) b exec
(gdb) c
(gdb) bt
#0 exec (path=0x1c "/init", argv=0x87fdeef0) at exec.c:16
#1 0x800272fc in sys_exec () at sysfile.c:473
#2 0x80026310 in syscall () at syscall.c:152
#3 0x800277d4 in swi_handler (r=0x87fdefb8) at trap.c:14
#4 0x80027634 in trap_swi ()
#5 0x00000010 in ?? ()
// 最初のexecはinit
// trap_swi はソフトウェアインタラクト(armのsyscallの命令)
(gdb) l start
// l _start は アンセブラ
169 _puts("starting xv6 for ARM...\n");
// OS経由じゃなく直接 uart を叩いて出力している
183 set_bootpgtbl(VEC_TBL, 0, 1 << PDE_SHIFT, 0);
184 set_bootpgtbl(KERNBASE+DEVBASE, DEVBASE, DEV_MEM_SZ, 1);
// 一番最初の page table を設定
186 load_pgtlb (kernel_pgtbl, user_pgtbl);
// 仮想メモリで動くようになる
// ただし、実アドレスと仮想アドレスが同じアドレスで走る
192 kmain ();
// void kmain (void) { に飛ぶ
(gdb) l kmain
29 init_vmm ();
30 kpt_freerange (align_up(&end, PT_SZ), vectbl);
31 kpt_freerange (vectbl + PT_SZ, P2V_WO(INIT_KERNMAP));
32 paging_init (INIT_KERNMAP, PHYSTOP);
// 仮想メモリマネージャ(vmm) を初期化
// freepage を仮想メモリ(vmm)に登録
// 次のkmem_init までが仮想メモリの初期化
37 trap_init (); // vector table and stacks for models
// (ソフト|ハード)インタラクトの飛び先の vector table を初期化
38 pic_init (P2V(VIC_BASE)); // interrupt controller
// 割り込みできるようにする
39 uart_enable_rx (); // interrupt for uart
// ic(集積回路) を割り込めるようにする
// ユーザーが入力した key は割り込みで kernel に伝えられる
40 consoleinit (); // console
// uart が初期化されたので console が起動できる
50 sti ();
// sti(Set Interrupt Flag) で 割り込みが許可される
// uart_enableはic(集積回路)の許可
// cpu の割り込みを許可
52 userinit();
// init のプロセスをロードして入れてるだけ
// 実行されるのは scheduler が起動されてから
(gdb) l userinit
122 p = allocproc();
// process 構造体を取ってくる
// kernel の alloc
125 if((p->pgdir = kpt_alloc()) == NULL) {
126 panic("userinit: out of memory?");
127 }
// kpt_alloc は kernelの page table の allocate
// つまり process と process のメモリ空間を allocate
129 inituvm(p->pgdir, _binary_initcode_start, (int)_binary_initcode_size
);
// user の仮想メモリを allocate
136 p->tf->r14_svc = (uint)error_init;
// trap flame(tf) を設定している
// trap flame は processの実行に必要
// process の中に current directory 入っている
// working directory は shell が持ってればいい
148 p->state = RUNNABLE;
// OS の p->state と同じ
// userinit() {ではprocess構造体をつくっただけ
// process に接続しただけで実行されてない
// 実際の実行は scheduler から行われる
(gdb) b scheduler
(gdb) c
(gdb) l
// この段階で sti(cpuの割り込み許可) されている
** swtch [#e1e72660]
(gdb) l swtchuvm
166 pushcli();
177 popcli();
// pushcli と popcli の間で割り込みを禁止している
174 asm("MCR p15, 0, %[v], c2, c0, 0": :[v]"r" (val):);
// MCR がpagetable を設定するアセンブラ
175 flush_tlb();
// userスペースにだけflush する(systemにはしない)
// gcc llvm の syntax
154 asm("MCR p15, 0, %[r], c8, c7, 0" : :[r]"r" (val):);
// asm も gdb 使えば追えるが大事なのは switch
(gdb) l swtch
// swtch はアセンブラなので出て来ない
(gdb) l scheduler
346 swtch(&cpu->scheduler, proc->context);
// swtch の引数はこの2つ
$ grep swtch *
// swtch はアセンブラなのでCでかけない
- src 側
$ vi swtch.S
zx
swtch:
STMFD r13!, {r4-r12, lr} // push svc r4-r12, lr to the stack
# switch the stack
STR r13, [r0] // save current sp to the old PCB (**old)
// store register
// r0に格納
// swtch の引数 cpu->scheduler がr0に入る
MOV r13, r1 // load the next stack
// swtch の引数 proc->context がr1に入る
# load the new registers. pc_usr is not restored here because
# LDMFD^ will switch mode if pc_usr is loaded. We just simply
# pop it out as pc_usr is saved on the stack, and will be loaded
# when we return from kernel to user space (swi or interrupt return)
LDMFD r13!, {r4-r12, lr} // pop svc r4-r12, lr
// ! マークは store を load したあと、r13の値を書き終わったとこに合わせる
# return to the caller
bx lr
// bx が抜けるためのコードかも?
// system call から戻るときはここは使わない
// 特権モードかはgdbでは確認できないけど、registerの状態を見れば何かわかるかも
- gdb側
(gdb) si
(gdb) x/1i $pc
=> 0x80025fd4 <swtch+16>: bx lr
(gdb) info register
cpsr 0x200000d3 536871123
// cpsr にcpu の値が入っている
(gdb) si
(gdb) info register
0x80025c98 in forkret () at proc.c:397
// forkret() に入る
(gdb) info register
// register の値は変わらない
// register の記述は C ではかけない
(gdb) l
395 // will swtch here. "Return" to user space.
// ここで切り替わる
403 if (first) {
// ここでif 文で分岐してるのはバグよけ
// 本来は省略できる
(gdb) n
0x80027634 in trapret ()
// n を 繰り返すと trapret に戻る
** trapret [#d3af8d11]
(gdb) bt
#0 0x80027634 in trapret ()
#1 0x8002077c in popcli () at arm.c:83
#2 0xe59f2024 in ?? ()
// popcli から trapret に来ている
// user スペースに切り替えたから信用はできない
- src 側
$ grep trapret *
// trap_asm.S がある
$ less trap_asm.S
trapret:
LDMFD r13, {sp, lr}^ // restore user mode sp and lr
ADD r13, r13, #8
LDMFD r13!, {r14} // restore r14
LDMFD r13!, {r2} // restore spsr
MSR spsr_cxsf, r2
// 特権モードを抜ける
LDMFD r13!,{r0-r12, pc}^ // restore context and return
// pc も r13 から取って来ている
// trapret 全体が割り込みから抜けるルーチンになっている
// user スペースに戻る
- gdb側
(gdb) b trapret
(gdb) c
(gdb) p *proc
$2 = {sz = 4096, pgdir = 0x800dfc00, kstack = 0x87fde000 "",
state = RUNNING, pid = 1, parent = 0x0, tf = 0x87fdefb8,
context = 0x87fdef88, chan = 0x0, killed = 0, ofile = {
0x0 <repeats 16 times>}, cwd = 0x800ac508 <icache+52>,
name = "initcode\000\000\000\000\000\000\000"}
// プロセスの構造体
// アセンブラで書いたinitcode は中でexecされる
(gdb) b exec
(gdb) si
(gdb) x/20i $pc
=> 0x4: ldr r2, [pc, #36] ; 0x30
0x8: mov r0, #7
0xc: svc 0x00000000
0x10: mov r0, #2
0x14: svc 0x00000000
0x18: b 0x10
0x1c: stmdbvs lr!, {r0, r1, r2, r3, r5, r8, r11, sp, lr}^
0x20: andeq r0, r0, r4, ror r0
0x24: andeq r0, r0, r12, lsl r0
0x28: andeq r0, r0, r0
0x2c: andeq r0, r0, r12, lsl r0
0x30: andeq r0, r0, r4, lsr #32
0x34: andeq r0, r0, r0
0x38: andeq r0, r0, r0
0x3c: andeq r0, r0, r0
0x40: andeq r0, r0, r0
0x44: andeq r0, r0, r0
0x48: andeq r0, r0, r0
0x4c: andeq r0, r0, r0
0x50: andeq r0, r0, r0
// これが出るまで si と x/20i $pc 繰り返す
// svc が software interact
-src側
$ vi initcode.S
start:
LDR r1, =init
LDR r2, =argv
MOV r0, #SYS_exec
SWI 0x00
// これが一番最初に動くコード
-gdb側
(gdb) x/s $r1
0x1c: "/init"
// $r1 で init にexecする
(gdb) si
0x80027614 in trap_swi ()
// system call に切り替わった
// trap したので interact の vector に入ったはず
(gdb) x/20i $pc
// user プロセスはスタートした状態でsystem に戻って来てる
(gdb) si
swi_handler (r=0x0) at trap.c:10
// ここにくるまで si
(gdb) l
// 割り込みはかかっている状態
(gdb) info register
// init 探せない
// initcode.asm は user 側には入ってない
(gdb) s
syscall () at syscall.c:147
147 num = proc->tf->r0;
// proc の trapframe に色々入っている
(gdb) p *proc->tf
$4 = {sp_usr = 4096, lr_usr = 0, r14_svc = 16, spsr = 1610612816, r0 = 7,
r1 = 28, r2 = 36, r3 = 0, r4 = 0, r5 = 0, r6 = 0, r7 = 0, r8 = 0,
r9 = 0, r10 = 0, r11 = 0, r12 = 0, pc = 16}
// こうすると register の値がみれる
// r1 = 28 に initcode が入っている
(gdb) x/s 28
0x1c: "/init"
// 確認
- src側(initcode.S)
9 LDR r1, =init
// user 空間で コンパイルされている
// kernel 空間はもっと高いアドレスにあるはず
- gdb
(gdb) s
151 if((num > 0) && (num <= NELEM(syscalls)) && syscalls[num]) {
// C のコードに移ってるので s でいける
// syscalls[] 配列は ポインタ
(gdb) s
sys_exec () at sysfile.c:448
448 if(argstr(0, &path) < 0 || argint(1, (int*)&uargv) < 0){
// sys_exec まで来た
// argv はポインタ
(gdb) l
473 return exec(path, argv);
// exec へ リターン
** fetchint [#c0829cf0]
(gdb) l fetchint
// kernel 側から指定されたプロセスのアドレスから integer を取ってくるサブルーチン
36 *pp = (char*)addr;
// *だから userプロセスのアドレスを*pp に入れてる
(gdb) s
27 if ((ip = namei(path)) == 0) {
(gdb) p path
$5 = 0x1c "/init"
// nameiのパスはuser空間にある
// 割り込みは解除してないのでkernelにはいかない
(gdb) l
33 // Check ELF header
34 if (readi(ip, (char*) &elf, 0, sizeof(elf)) < sizeof(elf)) {
35 goto bad;
36 }
// elf : linux のa.out
// namei はファイルシステムだから 書かないとexec 動かない
// 長いコードなのでここから先のテストはかなり難しい
78 if ((sz = allocuvm(pgdir, sz, sz + 2 * PTE_SZ)) == 0) {
// exec した時の引数をプロセス空間と別のとこにコピってる
// 再利用できない
// 正しい場所にコピって実行する必要がある
82 clearpteu(pgdir, (char*) (sz - 2 * PTE_SZ));
// allocate してから クリアしていく
// allocateuvm で物理アドレスは確保されてるが、page entry が切り替わってない(argvはuser空間に残っている)
94 if (copyout(pgdir, sp, argv[argc], strlen(argv[argc]) + 1) < 0) {
// copyoutでkernelにコピーしてる
** copyout [#p6e8b9d3]
(gdb) l copyout
// current page table に乗ってたら memcopy でできるけど、乗ってないからcopyoutする
414 pa0 = uva2ka(pgdir, (char*) va0);
// uva2ka で allocate した物理メモリを計算
426 memmove(pa0 + (va - va0), buf, n);
// uva2ka で計算したとこに memmoveする
66 if (loaduvm(pgdir, (char*) ph.vaddr, ip, ph.off, ph.filesz) < 0) {
// allocate して読み込んでそれを loaduvmでコピってる
71 iunlockput(ip);
// ip は namei から
// exec の途中で書き換えられないように lockしてたのを解除
130 freevm(oldpgdir);
// oldpgdir exec する前のもの
// execで新しいバイナリで古いバイナリをかき潰すことはしない(再利用しない)
// 新しく allocate してそこでloadしてる
(gdb) b 131
Breakpoint 4 at 0x80022548: file exec.c, line 131.
(gdb) c
// user 空間に init が load された状態
(gdb) si
0x80026310 in syscall () at syscall.c:152
152 ret = syscalls[num]();
// syscall から return される
(gdb) l 157
// copyout されてない
// さっきcopyout したのは新しいtrap frame に書き込まなきゃ行けなかったから
// exec の場合はr0に入れちゃ行けない(argc,argvが入ってる)
(gdb) n
swi_handler (r=0x87fdefb8) at trap.c:15
15 if (proc->killed)
(gdb) n
Breakpoint 2, 0x80027634 in trapret ()
// trapret () はアセンブラで書かれてるので C で breakpoint かけないと行き過ぎる
(gdb) si
0x00000004 in ?? ()
// trapret 抜けるまでsi
(gdb) x/20i 0
//ここでようやく最初のinit が動く