6.828: Operating System Engineering (17)

Exercise 3

cprintf 関数の中の

	va_start(ap, fmt);

した時点で

  • fmt は "x %d, y %x, z %d\n" な文字列の先頭アドレスを指していて
  • ap は二番目の引数なスタックの番地を指してます

次。cons_putc, va_arg, and vcprintf の順で云々という記述があるな。とりあえず stdarg.h なマクロはおおよそ理解できたのでスルー。

Exercise 4

はニガ手な endian に関する問題らしい

    unsigned int i = 0x00646c72;
    printf("H%x Wo%s", 57616, &i);

別でプログラム作って実行してみたら以下。

$ ./test
He110 World$

0xe110 って確かに 57616 ですね。i も gdb で確認。

(gdb) b main
Breakpoint 1 at 0x4004fc: file main.c, line 5.
(gdb) r
Starting program: /home/rms/Documents/debian/boutC/3.printf/test 

Breakpoint 1, main () at main.c:5
5           unsigned int i = 0x00646c72;
(gdb) n
6           printf("H%x Wo%s", 57616, &i);
(gdb) p &i
$1 = (unsigned int *) 0x7fffffffdbcc
(gdb) x/x 0x7fffffffdbcc
0x7fffffffdbcc: 0x00646c72
(gdb) x/c 0x7fffffffdbcc
0x7fffffffdbcc: 114 'r'
(gdb) x/c 0x7fffffffdbcd
0x7fffffffdbcd: 108 'l'
(gdb) x/c 0x7fffffffdbce
0x7fffffffdbce: 100 'd'
(gdb) x/c 0x7fffffffdbcf
0x7fffffffdbcf: 0 '\000'
(gdb) 

メモリの中では

726c6400

って形で入ってる、って意味でしたっけorz

Exercise 5

ここは cprintf 呼び出し元のスタックフレームというかローカル変数または戻り番地を指す形になるのかどうか。

Exercise 6

引数格納する順が変わるのであれば、stdarg.h なマクロの記述が変わってきますね。可変長引数の直前の引数の位置が分からない、ということは結構キビシい気がしますがどうなのでしょうか。

Challenge

以下な記述あり。

Enhance the console to allow text to be printed in different colors. The traditional way to do this is to make it interpret ANSI escape sequences embedded in the text strings printed to the console, but you may use any mechanism you like. There is plenty of information on the 6.828 reference page and elsewhere on the web on programming the VGA display hardware. If you're feeling really adventurous, you could try switching the VGA hardware into a graphics mode and making the console draw text onto the graphical frame buffer.

なんか frame buffer が云々って見えるんですがorz
これは置いといて先に進もう。

Exercise 9

Determine where the kernel initializes its stack, and exactly where in memory its stack is located. How does the kernel reserve space for its stack? And at which "end" of this reserved area is the stack pointer initialized to point to?

このあたりのソレを確認してみれば良いのかな。

relocated:

	# Clear the frame pointer register (EBP)
	# so that once we get into debugging C code,
	# stack backtraces will be terminated properly.
	movl	$0x0,%ebp			# nuke frame pointer

	# Set the stack pointer
	movl	$(bootstacktop),%esp

bootstacktop も entry.S で定義されてますね。

###################################################################
# boot stack
###################################################################
	.p2align	PGSHIFT		# force page alignment
	.globl		bootstack
bootstack:
	.space		KSTKSIZE
	.globl		bootstacktop   
bootstacktop:

どっかで見たと思っていたのですがメモリマップなマンガが memlayout.h にあったので以下に引用しておきます。

/*
 * Virtual memory map:                                Permissions
 *                                                    kernel/user
 *
 *    4 Gig -------->  +------------------------------+
 *                     |                              | RW/--
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     :              .               :
 *                     :              .               :
 *                     :              .               :
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| RW/--
 *                     |                              | RW/--
 *                     |   Remapped Physical Memory   | RW/--
 *                     |                              | RW/--
 *    KERNBASE ----->  +------------------------------+ 0xf0000000
 *                     |  Cur. Page Table (Kern. RW)  | RW/--  PTSIZE
 *    VPT,KSTACKTOP--> +------------------------------+ 0xefc00000      --+
 *                     |         Kernel Stack         | RW/--  KSTKSIZE   |
 *                     | - - - - - - - - - - - - - - -|                 PTSIZE
 *                     |      Invalid Memory (*)      | --/--             |
 *    ULIM     ------> +------------------------------+ 0xef800000      --+
 *                     |  Cur. Page Table (User R-)   | R-/R-  PTSIZE
 *    UVPT      ---->  +------------------------------+ 0xef400000
 *                     |          RO PAGES            | R-/R-  PTSIZE
 *    UPAGES    ---->  +------------------------------+ 0xef000000
 *                     |           RO ENVS            | R-/R-  PTSIZE
 * UTOP,UENVS ------>  +------------------------------+ 0xeec00000
 * UXSTACKTOP -/       |     User Exception Stack     | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebff000
 *                     |       Empty Memory (*)       | --/--  PGSIZE
 *    USTACKTOP  --->  +------------------------------+ 0xeebfe000
 *                     |      Normal User Stack       | RW/RW  PGSIZE
 *                     +------------------------------+ 0xeebfd000
 *                     |                              |
 *                     |                              |
 *                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                     .                              .
 *                     .                              .
 *                     .                              .
 *                     |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
 *                     |     Program Data & Heap      |
 *    UTEXT -------->  +------------------------------+ 0x00800000
 *    PFTEMP ------->  |       Empty Memory (*)       |        PTSIZE
 *                     |                              |
 *    UTEMP -------->  +------------------------------+ 0x00400000      --+
 *                     |       Empty Memory (*)       |                   |
 *                     | - - - - - - - - - - - - - - -|                   |
 *                     |  User STAB Data (optional)   |                 PTSIZE
 *    USTABDATA ---->  +------------------------------+ 0x00200000        |
 *                     |       Empty Memory (*)       |                   |
 *    0 ------------>  +------------------------------+                 --+
 *
 * (*) Note: The kernel ensures that "Invalid Memory" (ULIM) is *never*
 *     mapped.  "Empty Memory" is normally unmapped, but user programs may
 *     map pages there if desired.  JOS user programs map pages temporarily
 *     at UTEMP.
 */

上記にも記述がある、KSTACKTOP とか KSTKSIZE の定義が inc/memlayout.h で以下。

#define VPT		(KERNBASE - PTSIZE)
#define KSTACKTOP	VPT
#define KSTKSIZE	(8*PGSIZE)   		// size of a kernel stack

KERNBASE は 0xF0000000 で

// All physical memory mapped at this address
#define	KERNBASE	0xF0000000

ええと PTSIZE は inc/mmu.h で以下な定義。

// Page directory and page table constants.
#define NPDENTRIES	1024		// page directory entries per page directory
#define NPTENTRIES	1024		// page table entries per page table

#define PGSIZE		4096		// bytes mapped by a page
#define PGSHIFT		12		// log2(PGSIZE)

#define PTSIZE		(PGSIZE*NPTENTRIES) // bytes mapped by a page directory entry
#define PTSHIFT		22		// log2(PTSIZE)

4096 かける 1024 ということでよいのかな。と思ったらマンガに以下な記述あり。

 *    KERNBASE ----->  +------------------------------+ 0xf0000000
 *                     |  Cur. Page Table (Kern. RW)  | RW/--  PTSIZE
 *    VPT,KSTACKTOP--> +------------------------------+ 0xefc00000      --+
 *                     |         Kernel Stack         | RW/--  KSTKSIZE   |
 *                     | - - - - - - - - - - - - - - -|                 PTSIZE
 *                     |      Invalid Memory (*)      | --/--             |
 *    ULIM     ------> +------------------------------+ 0xef800000      --+

うう、計算できんorz
とりあえず ebp は 0 で esp が 0xefc00000 なことが確認できれば良いのかな。

動作確認

なんとなく忘れている。
とりあえず、ある screen な端末で

$ make qemu-gdb
***
*** Now run 'gdb'.
***
/opt/bin/qemu -hda obj/kern/kernel.img -serial mon:stdio -S -gdb tcp::26000

して、違う screen な端末で

$ gdb

しておいて、bootloader 相手にしないのであれば kernel に jmp する直前で止めて si すりゃ良いのかな。

(gdb) b *0x7d84
Breakpoint 1 at 0x7d84
(gdb) c
Continuing.
The target architecture is assumed to be i386
=> 0x7d84:      call   *%eax

Breakpoint 1, 0x00007d84 in ?? ()
(gdb) si
=> 0x10000c:    movw   $0x1234,0x472
0x0010000c in ?? ()
(gdb) 

これ、最初から *0x10000c に breakpoint 貼れば良いのかどうか。
とりあえず relocated ラベルまで進めます。

(gdb) si
=> 0x10000c:    movw   $0x1234,0x472
0x0010000c in ?? ()
(gdb) si
=> 0x100015:    lgdtl  0x110018
0x00100015 in ?? ()
(gdb) si
=> 0x10001c:    mov    $0x10,%eax
0x0010001c in ?? ()
(gdb) si
=> 0x100021:    mov    %eax,%ds
0x00100021 in ?? ()
(gdb) si
=> 0x100023:    add    %al,(%eax)
0x00100023 in ?? ()
(gdb) si
=> 0x100025:    add    %al,(%eax)
0x00100025 in ?? ()
(gdb) si
=> 0x100027:    add    %al,(%eax)
0x00100027 in ?? ()
(gdb) si
=> 0xf010002e <relocated>:      mov    $0x0,%ebp
relocated () at kern/entry.S:61
61              movl    $0x0,%ebp                       # nuke frame pointer
(gdb) 

で、

(gdb) si
=> 0xf0100033 <relocated+5>:    mov    $0xf0110000,%esp
relocated () at kern/entry.S:64
64              movl    $(bootstacktop),%esp
(gdb) si
=> 0xf0100038 <relocated+10>:   call   0xf010013a <i386_init>
67              call    i386_init
(gdb) 

ここで ebp および esp を確認してみれば良いのか。

(gdb) info register
eax            0x10     16
ecx            0x0      0
edx            0x8d     141
ebx            0x10094  65684
esp            0xf0110000       0xf0110000
ebp            0x0      0x0
esi            0x10094  65684
edi            0x0      0
eip            0xf0100038       0xf0100038 <relocated+10>
eflags         0x6      [ PF ]
cs             0x8      8
ss             0x10     16
ds             0x10     16
es             0x10     16
fs             0x10     16
gs             0x10     16
(gdb) 

あら? って思ったら、ここでも load と link の違いなソレなのかなぁ。

Exercise 10

なんか微妙な気がしますが構わず続けます。

To become familiar with the C calling conventions on the x86, find the address of the test_backtrace function in obj/kern/kernel.asm, set a breakpoint there, and examine what happens each time it gets called after the kernel starts. How many 32-bit words does each recursive nesting level of test_backtrace push on the stack, and what are those words?

Note that, for this exercise to work properly, you should be using the patched version of QEMU available on the tools page or on Athena. Otherwise, you'll have to manually translate all breakpoint and memory addresses to linear addresses.

これはまた何とゆーか面白そうなナニが仕込んであるのかどうなのか。って、init.c に以下が仕込んであるのを発見。

void
i386_init(void)
{
	extern char edata[], end[];

	// Before doing anything else, complete the ELF loading process.
	// Clear the uninitialized global data (BSS) section of our program.
	// This ensures that all static/global variables start out zero.
	memset(edata, 0, end - edata);

	// Initialize the console.
	// Can't call cprintf until after we do this!
	cons_init();

	cprintf("6828 decimal is %o octal!\n", 6828);







	// Test the stack backtrace function (lab 1 only)
	test_backtrace(5);

まぢですか。とりあえず最初から。というか breakpoint 貼る番地が分からん。

(gdb) b test_backtrace
Breakpoint 2 at 0xf01000e7: file kern/init.c, line 14.
(gdb)

できた?

(gdb) c
Continuing.
=> 0xf01000e7 <test_backtrace+10>:      mov    %ebx,0x4(%esp)

Breakpoint 2, test_backtrace (x=5) at kern/init.c:14
14              cprintf("entering test_backtrace %d\n", x);
(gdb) 

できたみたい。ええと、kern/init.c とのことなのか。手続き定義てきには以下?

// Test the stack backtrace function (lab 1 only)
void
test_backtrace(int x)
{
	cprintf("entering test_backtrace %d\n", x);
	if (x > 0)
		test_backtrace(x-1);
	else
		mon_backtrace(0, 0, 0);
	cprintf("leaving test_backtrace %d\n", x);
}

x は 5 らしい。stack の状態見てみれば良いの?

(gdb) info registers
eax            0x0      0
ecx            0x3d5    981
edx            0x3d5    981
ebx            0x5      5
esp            0xf010ffc0       0xf010ffc0
ebp            0xf010ffd8       0xf010ffd8
esi            0x10094  65684
edi            0x0      0
eip            0xf01000e7       0xf01000e7 <test_backtrace+10>
eflags         0x86     [ PF SF ]
cs             0x8      8
ss             0x10     16
ds             0x10     16
es             0x10     16
fs             0x10     16
gs             0x10     16
(gdb) 

む。以下に着目して確認。

esp            0xf010ffc0       0xf010ffc0
ebp            0xf010ffd8       0xf010ffd8

ええと stack のてっぺんは 0xf0110000 だったですね。とりあえずベースポインタについて確認してみました。

(gdb) x/x 0xf010ffd8
0xf010ffd8 <bootstack+32728>:   0xf010fff8
(gdb) x/x 0xf010fff8
0xf010fff8 <bootstack+32760>:   0x00000000
(gdb) 

i386_init の stack frame な base pointer が 0xf020fff8 な模様。
kernel.asm の test_backtrace な記述が以下。

// Test the stack backtrace function (lab 1 only)
void
test_backtrace(int x)
{
f01000dd:	55                   	push   %ebp
f01000de:	89 e5                	mov    %esp,%ebp
f01000e0:	53                   	push   %ebx
f01000e1:	83 ec 14             	sub    $0x14,%esp
f01000e4:	8b 5d 08             	mov    0x8(%ebp),%ebx
	cprintf("entering test_backtrace %d\n", x);
f01000e7:	89 5c 24 04          	mov    %ebx,0x4(%esp)
f01000eb:	c7 04 24 32 1a 10 f0 	movl   $0xf0101a32,(%esp)
f01000f2:	e8 34 08 00 00       	call   f010092b <cprintf>

今停止しているのは f01000e7 番地。スタック操作なあたりは当たり前ですがスルーなんですね。上記コードによれば

  • 現状 ebp には直前フレームの ebp の値が格納されている
    • 直前フレームを push した時点の esp の値?
  • esp は ebx を push して 0x14 を引いてるので値の差分としては正しい
  • esp はスタックのてっぺん (というか底) なので加算の形でローカル変数にアクセスできる

ここで止める

今日、へろへろながらも好調気味に進めてたのですが、そろそろ限界。