前言
最近在學習清華大學操作系統課程,同時在實驗樓做實驗,打算把實驗過程記錄下來。算上環境搭建的實驗共九個,也就是說這系列共有9篇文章。
貼幾張圖片:
1、瞭解彙編
運行 gcc -S -m32 lab0_ex1.c
,生成S彙編語言文件。
- -S表示僅僅編譯,不進行鏈接或彙編
- -m32表示生成32位機器的彙編代碼
得到lab_ex1.S文件,下面對比理解C文件和S文件(不會):
int count=1;
int value=1;
int buf[10];//三個變量
void main()
{
asm(
"cld \n\t"//將標誌寄存器Flag的方向標誌位DF清零。
"rep \n\t"//重複前綴指令
"stosl"//將EAX中的值保存到ES:EDI指向的地址中
:
: "c" (count), "a" (value) , "D" (buf[0])
:
);
}
.file "lab0_ex1.c"
.globl count
.data
.align 4
.type count, @object
.size count, 4
count:
.long 1
.globl value
.align 4
.type value, @object
.size value, 4
value:
.long 1
.comm buf,40,32
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
pushl %edi
pushl %ebx
.cfi_offset 7, -12
.cfi_offset 3, -16
movl count, %edx
movl value, %eax
movl buf, %ebx
movl %edx, %ecx
movl %ebx, %edi
#APP
# 6 "lab0_ex1.c" 1
cld
rep
stosl
# 0 "" 2
#NO_APP
popl %ebx
.cfi_restore 3
popl %edi
.cfi_restore 7
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
.section .note.GNU-stack,"",@progbits
2、用gdb調試
gcc -g -m32 lab0_ex2.c // -g表示調試
gdb a.out
先用 gdb l
查看代碼,發現是helloworld:
1 #include <stdio.h>
2 int
3 main(void)
4 {
5 printf("Hello, world!\n");
6 return 0;
7 }(gdb)
給每一行都加入斷點,輸入 info breakpoints
查看,有雖然每行都有斷點,但從地址上看實際上只有三處有斷點:
gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048426 in main at lab0_ex2.c:1
breakpoint already hit 1 time
2 breakpoint keep y 0x08048426 in main at lab0_ex2.c:2
3 breakpoint keep y 0x08048426 in main at lab0_ex2.c:3
4 breakpoint keep y 0x08048426 in main at lab0_ex2.c:4
5 breakpoint keep y 0x08048426 in main at lab0_ex2.c:5
6 breakpoint keep y 0x08048432 in main at lab0_ex2.c:6
7 breakpoint keep y 0x08048437 in main at lab0_ex2.c:7
按 r
(run)從頭運行,停止在第一個斷點。這裏:
- 按
c
(continue)繼續運行,直到遇見下一個斷點 - 按
s
(step)表示執行下一語句,會進入函數內部,類似VS的F11
- 按
n
(next)表示執行下一語句,但不會進入函數內部,類似VS的F10
按下 s
後屏幕輸出 hello,world!
停止在了斷點6,再按 s
停止在斷點7反花括號那裏,最後一步程序結束運行。按 q
退出。
3、掌握指針和類型轉換相關的C編程
實驗給出了一個C文件,讓debug,我的註釋如下:
#include <stdio.h>
#define STS_IG32 0xE // 32-bit Interrupt Gate
#define STS_TG32 0xF // 32-bit Trap Gate
typedef unsigned uint32_t;
#define SETGATE(gate, istrap, sel, off, dpl) { \
(gate).gd_off_15_0 = (uint32_t)(off) & 0xffff; \
(gate).gd_ss = (sel); \
(gate).gd_args = 0; \
(gate).gd_rsv1 = 0; \
(gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \
(gate).gd_s = 0; \
(gate).gd_dpl = (dpl); \
(gate).gd_p = 1; \
(gate).gd_off_31_16 = (uint32_t)(off) >> 16; \
}
/* Gate descriptors for interrupts and traps */
struct gatedesc {
unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment
unsigned gd_ss : 16; // segment selector
unsigned gd_args : 5; // # args, 0 for interrupt/trap gates
unsigned gd_rsv1 : 3; // reserved(should be zero I guess)
unsigned gd_type : 4; // type(STS_{TG,IG32,TG32})
unsigned gd_s : 1; // must be 0 (system)
unsigned gd_dpl : 2; // descriptor(meaning new) privilege level
unsigned gd_p : 1; // Present
unsigned gd_off_31_16 : 16; // high bits of offset in segment
}; // 共 64 位
int main(void)
{
unsigned before;
unsigned intr;
unsigned after;// unsigned 是 32 位
struct gatedesc gintr;
intr=8;
before=after=0;
gintr=*((struct gatedesc *)&intr);
/*先取 intr 地址,將其類型轉換爲指向結構體的地址類型,再引用就獲得了一個結構體gintr。
現在 gintr 的值爲:
gintr = {gd_off_15_0 = 8, gd_ss = 0, gd_args = 0, gd_rsv1 = 0, gd_type = 0,
gd_s = 0, gd_dpl = 0, gd_p = 0, gd_off_31_16 = 0}
*/
SETGATE(gintr, 0,1,2,3);
/*改變 gintr 這個結構體的成員變量,現在 gintr 的值爲 :
gintr = {gd_off_15_0 = 2, gd_ss = 1, gd_args = 0, gd_rsv1 = 0,
gd_type = 14, gd_s = 0, gd_dpl = 3, gd_p = 1, gd_off_31_16 = 0}
*/
intr=*(unsigned *)&(gintr);
/*
intr 是 32位 u int 型,gintr 是 64位 結構體類型
現在 gintr 的後32位 被轉換爲了 u int型
由上一步結果知 gintr 的後32位二進制爲 0000 0000 0000 0010 0000 0000 0000 0001 ……
轉換爲16進製爲 0x00010002 轉換爲10進製爲 65538
*/
printf("intr is 0x%x\n",intr);
/*
這裏輸出結果爲 0x10002 display變量intr的結果也爲 65538 驗證了我們上一步的推理
*/
printf("gintr is 0x%llx\n",*(long long unsigned*)&(gintr));//我改後的代碼
// 輸出 0xee0000010002
//printf("gintr is 0x%llx\n",gintr); //題目原來代碼,這裏有錯
return 0;
}
首先編譯報錯爲:
題目的意思應該是輸出更改後的gintr這個結構體變量的內容 ,參照上面處理方法我把它強制類型轉換成llu型: * ( long long unsigned *) & gintr
就OK了。
解釋:首先是取gintr這個變量的地址,強制轉換爲指向llu型變量的指針,再引用這個地址就得到了llu型的變量。
輸出結果爲:
intr is 0x10002
gintr is 0xee0000010002
這個程序目的應該是用setgate函數修改結構體內容,不知道我這樣改對不對。。。
另外注意到 gintr 調試輸出的高位爲0x00020001 ,高位放在了低地址,說明該機器是大端模式。
4、掌握通用鏈表結構相關的C編程
用在related_info/lab0/list.h中定義的結構和函數來實現一個小應用程序完成一個基於此鏈表的數據對象的訪問操作。 可參考related_info/lab0/lab0_ex4.c
查看lab0_ex4.c代碼:
struct list_entry {
struct list_entry *prev, *next;
};
typedef struct list_entry list_entry_t;
struct entry {
list_entry_t node;
int num;
};
int main() {
struct entry head;
list_entry_t* p = &head.node;
list_init(p);
//p->prev = p-> next = p
head.num = 0;
int i;
for (i = 1; i != 10; i ++) {
struct entry * e = (struct entry *)malloc(sizeof(struct entry));
e->num = i;
list_add(p, &(e->node));
/* list_add_after ( p , &(e -> node) ) =>>
__list_add (&(e -> node ) , p , p -> next) =>>
{
p->next = p->next->prev = &(e -> node );
&(e -> node )->next = p->next;
&(e -> node )->prev = p;
}
*/
p = list_next(p);//p = p->next
}
//reverse list all node
while ((p = list_prev(p)) != &head.node)
printf("%d\n", ((struct entry *)p)->num);
//從後往前打印節點的值: 10 9 8 ……
return 0;
}
分析:在進入循環體前,p是指向自己的頭指針。p的結構如下:
進入到循環體的第一次循環後,新建了一個節點,鏈接到p。結構如下:
第一次循環的最後,p = p -> next
也就是 p = e1
。第二次循環後結構如圖:
然後 p = e2
結束第二層循環,依此循環10次就完成了雙向鏈表節點的插入。最後從後往前打印p的num的值。
在實驗樓的終端上運行報錯,就沒調試。
總結
沒學過x86彙編,但是學過mips,所以第一題不會。指針和數據結構仍有些喫力,繼續加油。