Archive for August, 2008
OS Lab5 debugging notes
还算顺利,不过这个lab蛮无聊的,等有空了把syscall改成类似linux的做法,单一中断号+寄存器选择syscall。
1. 最花时间的一个bug是ls返回值没有改成应用程序数,结果一开始一直以为是brk系统调用没写好,最后才发现问题出在这么小的地方。
2. brk的逻辑还不是很清楚,尽管通过了简单的测试,但是debug输出的信息显示brk增长的很快,经常是一个页一个页涨的,看来还得查下brk的具体行为。
3. 写了个比MAGIC_BREAK好用一点的宏,因为用户态的程序都是按二进制读入的,Simics无法得到函数信息(函数名、当前行数等),利用C99的宏写了个新的INFO_BREAK
#define INFO_BREAK \
do { \
lprintf_kern("break in %s:%d", __FUNCTION__, __LINE__); \
MAGIC_BREAK; \
} while (0) \
x86汇编图形编程资料
http://www.faqs.org/faqs/assembly-language/x86/general/part1/
Subject: 12. Using VGA Mode 13h for Fast Graphics
12.1 INTRODUCTION AND PREPARATION
Mode 13h is so widely used for graphics applications in DOS because it
is very easy to use. The screen is constantly being redrawn by the
video card. To affect what the card draws, it is necessary to write to
the screen buffer. The screen buffer in mode 13h is always at
segment:offset = A000:0000. Thus, to set up drawing directly to the
video buffer, this is what you’d most often first do:
;Change the video mode to 13h
xor ah, ah ;VIDEO Function 00h: Change screen
mov al, 13h ;Put the desired graphics mode into AL
int 10h ;Call VIDEO
;Prepare for writing to the video buffer
mov di, 0a000h ;Put the video segment into DI
mov es, di ; so it can easily be put into ES
xor di, di ;Start writing at coordinates (0,0)
12.2 WRITING PIXELS TO THE SCREEN
Why is Mode 13h so popular? To understand, you must know a few basic
facts. In Mode 13h, the screen is 320 by 200, or 320 pixels across and
200 pixels down. In each pixel, there’s a possibility of 256 colors,
which can be fit into one byte. Thus, 320*200*1 = 64000 bytes, about
the size of one segment. Think of the screen as an array of colors.
The first row takes up addresses A000:0000 to A000:013F (decimal 319),
the second row takes up addresses A000:0140 to A000:027F (decimal 639),
and so on. To plot a pixel, assuming ES=A000:
;Plot a pixel in video mode 13h, where
;PixelAddress = (320 * Y) + X
mov ax, 320 ; Prepare for the multiplication
mul [Y] ; Assuming that Y is defined in the data segment
; earlier in the program
mov di, ax ; Put in into the pointer to the offset of ES
add di, [X] ; Assuming that X is defined in the data segment
; earlier in the program
mov al, [Color] ; Assuming that Color is defined in the data
; segment earlier in the program
stosb ; Write it to the screen!
See how easy that was? Something to remember is that it is zero-based.
The upper-left corner is (0,0), and the lower-right is (319,199). A
complete TASM Ideal mode procedure might look something like this (it
assumes that the video card is already set to mode 13h):
PROC WritePixel BASIC ; Or whatever language you might want to link
; it to
USES es, di ; It's always a good idea to preserve ES and DI
ARG X:word, Y:word, Color:BYTE
mov di, 0a000h ; Put the video segment into DI
mov es, di ; so it can easily be put into ES
mov ax, 320 ; Prepare for the multiplication
mul [Y] ; Offset pointer by the Y value passed in
mov di, ax ; Put in into pointer to the offset of ES
add di, [X] ; Offset the pointer by the X value passed in
mov al, [Color] ; Put color to be written to the screen in AL
stosb ; Write it to the screen!
ret
ENDP WritePixel
To write a horizontal line, just put the length in CX, and replace the
STOSB with a REP STOSB. Writing a vertical line is only a little
trickier. Observe the following TASM Ideal mode procedure:
PROC VerticalLine BASIC ; Or whatever language you might want to link
; it to
USES es, di ; It's always a good idea to preserve ES and
; DI
ARG X:word, Y:word, Color:BYTE, Length:word
mov di, 0a000h ; Put the video segment into DI
mov es, di ; so it can easily be put into ES
mov ax, 320 ; Prepare for the multiplication
mul [Y] ; Offset the pointer by the Y value passed in
mov di, ax ; Put in into the pointer to the offset of ES
add di, [X] ; Offset the pointer by the X value passed in
mov al, [Color] ; Put the color to be written to the screen
; in AL
mov cx, [Length] ; Prepare for the loop
YLoop:
stosb ; Write it to the screen!
add di, 319 ; Move down one row (DI has already advanced
; once because of the STOSB, thus the 319)
loop YLoop
ret
ENDP VerticleLine
Observe how there is a tight loop that moves DI down one row each
iteration.
In short, the easiest way to write directly to the Mode 13h video buffer
is to think of the screen as just a 320 by 200 array of bytes, starting
at A000:0000.
Author: Michael Averbuch (mikeaver@prairienet.org)
Last Change: 29 Dec 94
OS Lab4 debugging notes [2]
系统调用 exec()
1. 清空页表的用户空间映射的函数一开始写得yts,bug到处都是,比如free的时候没使用指向内存块首地址的指针,记录内存地址的变量没有累加。
2. exec传递给内核态的两个参数必须先在内核态保存一个副本,否则清空用户态页表后就无法得到这两个参数信息了。
3. 分配给用户态的页面必须先清零,一方面考虑到安全性,另一方面不清零会隐藏一些潜在的bug。一开始我没有在内核执行exec的时候完整的复制所有的参数,而是直接指向了原进程的内存空间,由于清空页表后再次申请新页表时得到了原来的页面,结果正好原来那个保存参数的页面和新进程的该页面重合了 =_= 于是浪费了不少时间在这个bug上
关于smalloc函数与malloc函数的区别
s前缀的malloc函数(包括smalloc、smemalign等)不记录分配块的大小,比较节省空间,但是要求用户在用sfree释放内存的时候指定被释放的内存块大小。
malloc则和libc中的同名函数很相似。
整个分配信息(包括哪些块已被使用)都记录在malloc_lmm这个全局变量中,内存被分为若干个region,每个region中有若干个nodes,这些信息可以通过lmm_dump查看(需要include <lmm.public.h>)。
smemalign很适合分配需要页对齐的内存块,因为如果使用memalign分配的话,每个页面就需要多用8字节的空间来记录当前块的大小了(保存在每个内存块的前面),会产生大量内存碎片。
Inside the Windows Vista Kernel
Part 1: http://technet.microsoft.com/en-us/magazine/cc162494.aspx
Part 2: http://technet.microsoft.com/en-us/magazine/cc162480.aspx
Part 3: http://technet.microsoft.com/en-us/magazine/cc162458.aspx
关于作者:
Mark Russinovich is a Technical Fellow at Microsoft in the Platform and Services Division. He is a coauthor of Microsoft Windows Internals (Microsoft Press, 2004) and a frequent speaker at IT and developer conferences. He joined Microsoft with the recent acquisition of the company he cofounded, Winternals Software. He also created Sysinternals, where he published the Process Explorer, Filemon, and Regmon utilities.
OS Lab 4 debugging notes [1]
系统调用 fork()
用Simics跟踪一条条汇编分析页表映射、寄存器值还真是体力活啊。。
1. 实现Copy On Write时,如果某一个用户态页面有多个进程共享,其中一个进程修改该页面时需要创建一个新的页面。一开始偶忘了把原来页面的内容复制到新的页面了 =_= 另外由于新的页面要代替老的页面,或者说它们的物理地址不同,但虚拟地址相同,我的方法是在内核态开辟一个大小为一个页面的空间作为中转。
2. do_fork函数中,子进程复制父进程的页表的同时会把父进程页表项置为不可写,注意最后要flush tlb。因为一开始没有flush tlb,导致最后用户态fork返回以后读取的信息来自于tlb,直接改写了共享页面中fork的返回地址,导致切换到子进程时fork的返回地址丢失。这个bug让我郁闷了两三个小时。。
3. 使用两次fork时,第二次fork返回的pid会被改掉。查了下发现为用户空间分配物理页面的代码里居然在分配好以后没有把对应的struct置为已使用,结果导致第二个子进程COW创建新页面时得到了原来的父进程页面,改写了父进程页面内容。
loader模块的bug?
下面这个程序,用elf_load_helper载入后rodate段的区间为0×80~0×224,都访问到kernel态了。。
int main(int argc, char *argv[]){
int pid, i;
pid = fork();
while (1);
// blah
return 0;
}
估计是因为编译器把while (1)后面的代码都视为dead code然后全部优化掉了(其实调试的时候可以jmp过去的)
结果这个程序就没rodata信息了,然后这个读取elf的函数就出错了?
MozBackup: 备份Mozilla软件的好东东
MozBackup is a simple utility for creating backups of Mozilla Firefox, Mozilla Thunderbird, Mozilla Sunbird, Flock, SeaMonkey, Mozilla Suite, Spicebird and Netscape profiles.
It allows you to backup and restore bookmarks, mail, contacts, history, extensions, cache etc. It’s an easy way to do Firefox backup, Thunderbird backup …
MozBackup is compatible with:
- Firefox 1.0 – 3.0
- Thunderbird 1.0 – 3.0 Alpha 1
- Sunbird 0.3 – 0.8
- Flock 1.0 – 1.1
- SeaMonkey 1.0a – 1.1
- Mozilla Suite 1.7 – 1.7.x
- Spicebird 0.4
- Netscape 7.x, 9.x
This program is freeware and works on Windows 98/ME/NT/2000/XP/2003/Vista. It’s developed in Czech republic.
[zz]asmlinkage & regparm
发信人: feiy (积极、乐观、率性、自信), 信区: KernelTech
标 题: asmlinkage 其他 regparm attribute函数的gcc汇编浅析
发信站: BBS 水木清华站 (Mon Apr 26 02:56:12 2004), 转信
[asmlinkage 其他 regparm attribute函数的gcc汇编浅析]
asmlinkage是一类特殊的regparm attribute函数:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
下面的分析基于:c 源码 tt.c, 汇编代码 tt.S, shell命令是
gcc -S tt.c -o tt.S gcc version 3.2.2, cpu 386+
1. C 源码
__attribute__((regparm(0~3))) int cal(int a, int b, int c, int d)
{
return a + b + c + d;
}
int main()
{
int r1=1;
int r2=2;
int r3=3;
int r4=4;
cal(r1, r2, r3, r4);
return 0;
}
2. 当__attribute__((regparm(0))) int cal(int a, int b, int c, int d)
gcc编译得到下述汇编代码
.file "tt.c"
.text
.globl cal
.type cal,@function
cal:
pushl %ebp
movl %esp, %ebp
movl 12(%ebp), %eax # 从 堆栈 中取出传递给函数的第二个参数 b
addl 8(%ebp), %eax # 从 堆栈 中取出传递给函数的第一个参数 a
addl 16(%ebp), %eax # 从 堆栈 中取出传递给函数的第三个参数 c
addl 20(%ebp), %eax # 从 堆栈 中取出传递给函数的第四个参数 d
leave
ret
.Lfe1:
.size cal,.Lfe1-cal
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movl $1, -4(%ebp)
movl $2, -8(%ebp)
movl $3, -12(%ebp)
movl $4, -16(%ebp)
pushl -16(%ebp) # 参数通过堆栈传递, 把要传递给函数的第四个参数压栈
pushl -12(%ebp) # 第三个参数入栈 注:这里的ebp用来指示main函数的局
pushl -8(%ebp) # 第二个参数入栈 部变量,-4(%ebp)存放的是第一
pushl -4(%ebp) # 第一个参数入栈 个局部变量r1,-4属于分配上的
call cal # 巧合, 这里的-4与r1将作为传递给函
# 数的第一个参数没有必然的关系。
addl $16, %esp
movl $0, %eax
leave
ret
.Lfe2:
.size main,.Lfe2-main
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
3. 当__attribute__((regparm(3))) int cal(int a, int b, int c, int d)
得到下述汇编代码
.file "tt.c"
.text
.globl cal
.type cal,@function
cal:
pushl %ebp
movl %esp, %ebp
subl $12, %esp
movl %eax, -4(%ebp) # 从 %eax 中取出传递给函数的第一个参数 a
movl %edx, -8(%ebp) # 从 %edx 中取出传递给函数的第二个参数 b
movl %ecx, -12(%ebp) # 从 %ecx 中取出传递给函数的第三个参数 c
movl -8(%ebp), %eax
addl -4(%ebp), %eax
addl -12(%ebp), %eax
addl 8(%ebp), %eax # 从 堆栈 中取出传递给函数的第四个参数 d
leave
ret
.Lfe1:
.size cal,.Lfe1-cal
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
subl %eax, %esp
movl $1, -4(%ebp)
movl $2, -8(%ebp)
movl $3, -12(%ebp)
movl $4, -16(%ebp)
subl $12, %esp
movl -12(%ebp), %ecx # 通过 ecx 向函数传递第三个参数 r3
movl -8(%ebp), %edx # 通过 edx 向函数传递第二个参数 r2
movl -4(%ebp), %eax # 通过 eax 向函数传递第一个参数 r1
pushl -16(%ebp) # 通过 堆栈 向函数传递第四个参数 r4
call cal
addl $16, %esp
movl $0, %eax
leave
ret
.Lfe2:
.size main,.Lfe2-main
.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"
4. 结论
1) __attribute__((regparm(0~3))) 用来指定函数调用时通过寄存器传递参
数的个数
2) if any, 从第一个参数起, 依此通过 eax, edx, ecx 传递参数,
3) 而多出的参数将通过堆栈传递. 堆栈传递总是最后一个先入栈.
汇编文件中导出函数符号
Linux 2.4.18的linux/linkage.h文件定义了若干相关的宏
#define SYMBOL_NAME(X) X
#ifdef __STDC__
#define SYMBOL_NAME_LABEL(X) X##:
#else
#define SYMBOL_NAME_LABEL(X) X/**/:
#endif
#define __ALIGN .align 16,0x90
#define __ALIGN_STR ".align 16,0x90"
#define ALIGN __ALIGN
#define ALIGN_STR __ALIGN_STR
#define ENTRY(name)
.globl SYMBOL_NAME(name);
ALIGN;
SYMBOL_NAME_LABEL(name)
用ENTRY(name)就能定义函数了。后来发现Flux OSKit中本来就提供了类似功能的宏,定义在inc/asm.h中。
使用的时候需要再写一个c语言的wrapper function(至少2.4.18里面是这么做的)
asmlinkage void ret_from_fork(void) __asm__("ret_from_fork");