了解操作系统开发实验环境,熟悉命令行方式的编译、调试工程,掌握基于硬件模拟器的调试技术,熟悉C语言编程和指针的概念,了解X86汇编语言。
1: 了解汇编;2: 用gdb调试;3: 掌握指针和类型转换相关的C编程;4: 掌握通用链表结构相关的C编程
运行 gcc -S -m32 lab0_ex1.c 生成 .s的汇编语言文件
得到 lab_ex1.s 汇编文件
对比汇编文件 与 C语言文件
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])
:
);
}
copy
由实验指导书可知
GCC扩展内联汇编的基本格式是:
asm [volatile] ( Assembler Template
: Output Operands
[ : Input Operands
[ : Clobbers ] ])
在输入部分没有 " = " 因此上面的C语言代码是用于将数据存入寄存器的代码
因此得到的主要汇编代码应该为:(以下为 lab_ex1.s 汇编文件的代码)
.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 ;1
movl value, %eax ;2
movl buf, %ebx ;3 这三行与上文所说的C语言代码相吻合
movl %edx, %ecx
movl %ebx, %edi
#APP
# 6 "lab0_ex1.c" 1
cld ;1
rep ;2
stosl ;3 这三行与上文所说的C语言代码相吻合
# 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
copy
可见通过运行C语言代码,实现了将C语言这种高级语言向汇编代码的转变,为后续的操作系统试验打下基础,实际了解和操作了将高级语言向低级语言转换的过程。
在控制台输入:
$cd ucore_lab/related_info/lab0
$gcc -g -m32 lab0_ex2.c
文件夹得到文件 a.out
控制台运行 ./a.out
在控制台得到输出 Hello,world!
继续深入实验,在 lab0_ex2.c C语言文件的每行代码设置断点进行观察:
控制台输入:
$gcc -g -m32 lab0_ex2.c -o lab0_ex2
$gdb lab0_ex2
(gdb) b 7
$(gdb) info breakpoints
最后得到运行结果
1 breakpoint keep y 0x08048426 in main at lab0_ex2.c:1 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
copy
可以发现直到第五行这段代码所分配的地址实际上是不变的,即直到 printf 出现以后才出现了内存上的变化,因此可以推断应该是在 printf 介入以后才在汇编语言中产生了内存的调用等活动,才会有内存的分配问题。通过这个练习了解了gdb调试的方法。
运行 gcc - g -m32 lab0_ex3.c 2>&1|tee make.log
错误报告存在 make.log 里面
lab0_ex3.c代码:
#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
};
int
main(void)
{
unsigned before;
unsigned intr;
unsigned after;
struct gatedesc gintr;
intr=8;
before=after=0;
gintr=*((struct gatedesc *)&intr);
SETGATE(gintr, 0,1,2,3);
intr=*(unsigned *)&(gintr);//intr是gintr的低
printf("intr is 0x%x\n",intr);
printf("gintr is 0x%llx\n",gintr);
return 0;
}
copy
发现报错
warning: format ‘%llx’ expects argument of type ‘long long unsigned int’, but argument 2 has type ‘struct gatedesc’
copy
由此可以判断是由于 gintr 的类型为所定义的结构体 gatedesc 所导致的类型不匹配无法输出,因此我们只需在输出的时候进行强制类型转化。
对 lab0_ex3.c 文件进行修改:
printf("gintr is 0x%llx\n",*(long long unsigned *)&(gintr));
copy
得到输出
intr is 0x10002 gintr is 0xee0000010002
copy
对代码进行分析:
通过调用 SETGATE 这个宏对 gintr 进行赋值
SETGATE(gintr, 0,1,2,3);
copy
将值代入下面式子,每行的结果计算于注释处
SETGATE(gate, istrap, sel, off, dpl) { (gate).gd_off_15_0 = (uint32_t)(2) & 0xffff;//0x0002 (gate).gd_ss = (1); //0x0001 (gate).gd_args = 0; //0b00000 (gate).gd_rsv1 = 0; //0b000 (gate).gd_type = (0) ? STS_TG32 : STS_IG32; //0xe (gate).gd_s = 0; //0b0 (gate).gd_dpl = (3); //0b11 (gate).gd_p = 1; //0b1 (gate).gd_off_31_16 = (uint32_t)(2) >> 16; //0x0000 }
copy
从高位到低位组合起来,依次是 0x0000 ee00 0001 0002
因此 intr 应该输出 0x10002 ; gintr 应该输出 0xee00 0001 0002
unsigned intr;
intr=8;
SETGATE(intr, 0,1,2,3);
copy
继续回答问题,请问执行上述指令后, intr的值是多少?
因此对 lab0_ex3.c 的文件进行修改
#include "stdlib.h"
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
};
typedef struct gatedesc gatedesc;
typedef unsigned int uint32_t;
#define STS_IG32 0xE // 32-bit Interrupt Gate
#define STS_TG32 0xF // 32-bit Trap Gate
#define SETGATE(gate, istrap, sel, off, dpl) { \
((gatedesc*)(&gate))->gd_off_15_0 = (uint32_t)(off) & 0xffff; \
((gatedesc*)(&gate))->gd_ss = (sel); \
((gatedesc*)(&gate))->gd_args = 0; \
((gatedesc*)(&gate))->gd_rsv1 = 0; \
((gatedesc*)(&gate))->gd_type = (istrap) ? STS_TG32 : STS_IG32; \
((gatedesc*)(&gate))->gd_s = 0; \
((gatedesc*)(&gate))->gd_dpl = (dpl); \
((gatedesc*)(&gate))->gd_p = 1; \
((gatedesc*)(&gate))->gd_off_31_16 = (uint32_t)(off) >> 16; \
}
int main()
{
unsigned intr;
intr=8;
SETGATE(intr, 0,1,2,3);
printf("%x", intr);
return 0;
}
copy
因为与前面的实验代码相似,因此可以预知的,输出应该是低32位的输出 0x10002
在控制台中输入
gcc -g -m32 lab0_ex3.c
./a.out
得到输出
0x10002
copy
与预期结果相同
通过这个实验了解通过:对 long long unsigned 和 unsigned int 类型进行逐位取值的分配与确定的操作,了解到了如何进行类型转化。
问题:
用在 related_info/lab0/list.h 中定义的结构和函数来实现一个小应用程序完成一个基于此链表的数据对象的访问操作。
那我们先对 list.h 文件阅读分析
struct list_entry {
struct list_entry *prev, *next;
};
copy
不难发现在此处的链表结构应该是双向循环链表结构,首位相接,有前向后向两个节点
static inline void list_init(list_entry_t *elm) __attribute__((always_inline));
static inline void list_add(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline));
static inline void list_add_before(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline));
static inline void list_add_after(list_entry_t *listelm, list_entry_t *elm) __attribute__((always_inline));
static inline void list_del(list_entry_t *listelm) __attribute__((always_inline));
static inline void list_del_init(list_entry_t *listelm) __attribute__((always_inline));
static inline bool list_empty(list_entry_t *list) __attribute__((always_inline));
static inline list_entry_t *list_next(list_entry_t *listelm) __attribute__((always_inline));
static inline list_entry_t *list_prev(list_entry_t *listelm) __attribute__((always_inline));
static inline void __list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) __attribute__((always_inline));
static inline void __list_del(list_entry_t *prev, list_entry_t *next) __attribute__((always_inline));
copy
里面已经定义好了一系列的函数对链表进行操作
我同过 lab0_ex4.c 文件进行对练习四的操作与检验
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
struct entry {
list_entry_t node;
int num;
};
int main() {
struct entry head;
list_entry_t* p = &head.node;
list_init(p);//初始化链表,创建头结点
head.num = 0;
int i;
for (i = 1; i != 10; i ++) {//创建9个节点
struct entry * e = (struct entry *)malloc(sizeof(struct entry));
e->num = i;
list_add(p, &(e->node));//调用添加节点的函数
p = list_next(p); //调用查询后继节点的函数
}
//reverse list all node
while ((p = list_prev(p)) != &head.node) //调用了前向函数,向前查询前向节点
printf("%d\n", ((struct entry *)p)->num);
return 0;
}
copy
值得一提的是在 list.h 代码段里有一处错误需要修改
#include "defs.h" //原本为 #include <defs.h>
copy
调用控制台命令 gcc -g -m32 lab0_ex4.c
我们可以得到输出
8 7 6 5 4 3 2 1
copy
但这其实与我们与预期的结果不相同
通过这段代码可知
for (i = 1; i != 10; i ++) {//进行了9次循环,i最大为9
struct entry * e = (struct entry *)malloc(sizeof(struct entry));
e->num = i;
list_add(p, &(e->node));
p = list_next(p);
}
copy
因此在此链表里面尾结点并没有打印出来,因此我对 lab0_ex4.c 进行了轻微的修改
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
struct entry {
list_entry_t node;
int num;
};
int main() {
struct entry head;
list_entry_t* p = &head.node;
list_init(p);//初始化链表,创建头结点
head.num = 0;
int i;
for (i = 1; i != 10; i ++) {//创建9个节点(除去头结点)
struct entry * e = (struct entry *)malloc(sizeof(struct entry));
e->num = i;
list_add(p, &(e->node));//调用添加节点的函数
p = list_next(p); //调用查询后继节点的函数
}
printf("%d\n", ((struct entry *)p)->num);
//reverse list all node
while ((p = list_prev(p)) != &head.node) //调用了前向函数,向前查询前向节点
printf("%d\n", ((struct entry *)p)->num);
return 0;
}
copy
在循环输出链表之前,再加一句输出,将当前节点的值进行输出,得到新的结果
9 8 7 6 5 4 3 2 1
copy
再对代码进行改进,尝试从前往后进行输出
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
struct entry {
list_entry_t node;
int num;
};
int main() {
struct entry head;
list_entry_t* p = &head.node;
list_init(p);//初始化链表,创建头结点
head.num = 0;
int i;
for (i = 1; i != 10; i ++) {//创建9个节点(除去头结点)
struct entry * e = (struct entry *)malloc(sizeof(struct entry));
e->num = i;
list_add(p, &(e->node));//调用添加节点的函数
p = list_next(p); //调用查询后继节点的函数
}
printf("%d\n", ((struct entry *)p)->num);
//reverse list all node
while ((p = list_prev(p)) != &head.node) //调用了前向函数,向前查询前向节点
printf("%d\n", ((struct entry *)p)->num);
while ((p = list_next(p)) != &head.node) //调用了后继函数,向前查询后继节点
printf("%d\n", ((struct entry *)p)->num);
return 0;
}
copy
运行得到输出
9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9
copy
由此,我们得到了一个具有头节点的,双向循环链表,通过实验四我们了解了如何定义一个结构体,创建自己想要的结构类型,为以后的内存的调用打下基础,学习和了解了双向循环链表的创建过程,在debug的过程中充分体验了双向链表的地址的变化过程,通过查看双向链表的地址,了解和学习了地址的分配的过程。
通过本次学习了解了gdb调试的方法;通过本次学习了解通过:对 long long unsigned 和 unsigned int 类型进行逐位取值的分配与确定的操作,了解到了如何进行类型转化。我们得到了一个具有头节点的,双向循环链表,通过实验四我们了解了如何定义一个结构体,创建自己想要的结构类型,为以后的内存的调用打下基础,学习和了解了双向循环链表的创建过程,在debug的过程中充分体验了双向链表的地址的变化过程,通过查看双向链表的地址,了解和学习了地址的分配的过程。
学习时间 1188分钟
操作时间 167分钟
按键次数 2679次
实验次数 50次
报告字数 12456字
是否完成 完成