清华ucore-lab0实验报告

学的很菜。。。。。。

Lab0实验报告

1.了解汇编

通过以下两条语句,将工作目录转到实验代码存放处,其中gcc -S选项是为了生成汇编文件,-m32选项则是指定编译为32位应用程序,结果如图所示,新生成了一个.s汇编文件
生成汇编文件

cd related_info/lab0
gcc -S -m32 lab0_ex1.c

.c文件中代码如下,首先定义两个变量,一个数组,然后向buf中写上count个value值。

int count=1;
int value=1;
int buf[10];
void main()			
{				
   asm(	
	"cld \n\t"
        "rep \n\t"					     
        "stosl"
	:
	: "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函数部分,首先将要用的寄存器的值先压入堆栈,对数据进行保护,随后将相关的数据,count、value、buf存入寄存器

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

然后进入操作阶段,首先cld命令对DF寄存器进行复位,决定接下来的操作向着内存增大的方向进行,随后rep表示重复,再结合stosl命令就可以实现向buf中写上count个value值

#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

最后是一些关于当前操作系统、使用的gcc编译器的注释说明

.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
	.section	.note.GNU-stack,"",@progbits

两者关系为,C语言文件经过编译后生成汇编文件,从而被计算机识别,实现对硬件的直接操控

2. 用gdb调试

观察实验2代码,发现是打印hello world的程序

#include <stdio.h>
int
main(void)
{
  printf("Hello, world!\n");
  return 0;
}

通过一下语句就可以运行,显然程序没有错误

gcc -g -m32 lab0_ex2.c
./a.out

结果如下,然后用gdb来调试
在这里插入图片描述
点击运行,结果正常且没有报错,再输入where命令寻找出错的地方,显示没有错误。在这里插入图片描述在这里插入图片描述
然后在程序里面加上一些语句,使得其能够通过编译,但是运行会出错,下图中会出现分母为0的错误
在这里插入图片描述
经过编译后,进入gbd调试模式,输入run的时候,hello world可以正常输出,但是后面报错,提示算术表达式错误,用where语句查找出错的地方,发现是第七行
在这里插入图片描述
随后用break 7语句在第七行设置断点,则下一次运行run的时候,遇到断点就会暂停,然后用next语句单条执行,随后就报出算术表达式错误
在这里插入图片描述

3. 掌握指针和类型转换相关的C编程

输入如下指令进行编译,代码中的 “2>&1”要拆开来看,对于& 1 更准确的说应该是文件描述符 1,而1标识标准输出,stdout。对于2 ,表示标准错误,stderr。所以2>&1 的意思就是将标准错误重定向到标准输出。tee命令用于读取标准输入的数据,并将其内容输出成文件,将错误日志保存到make.log方便后期查看

cd related_info/lab0
gcc -g -m32 lab0_ex3.c 2>&1|tee make.log

打开make.log,内容如下发现是48行处,printf语句内部出现了问题

lab0_ex3.c: In function \u2018main\u2019:
lab0_ex3.c:48:5: warning: format \u2018%llx\u2019 expects argument of type \u2018long long unsigned int\u2019, but argument 2 has type \u2018struct gatedesc\u2019 [-Wformat=]
     printf("gintr is 0x%llx\n",gintr);
     ^

打开具体代码查看,首先定义了一个关于结构体gatedesc的操作,输入一个结构体变量,三个常数,就可以改变结构体内相关参数的值,随后是结构体的具体定义,可以看出这个结构体长度为64位

#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
 };

随后进入主函数部分,第一行就是对无符号数intr取地址,再强制转换指针类型,再取出这个指针所指的变量,c语言编译器就是通过指针类型来弄清楚所指的这个变量到底长度多少个字节。随后用SETGATE改变结构体内部参数的值

gintr=*((struct gatedesc *)&intr);
SETGATE(gintr, 0,1,2,3);
intr=*(unsigned *)&(gintr);
printf("intr is 0x%x\n",intr);
printf("gintr is 0x%llx\n",gintr);

由于printf内的参数%llx功能是输出有符号64位16进制整数,所以对printf后面的gintr参数进行强制类型转换,仿照intr的转换语句,使得printf后面参数变为long long unsigned类型,加在其下一行,再次编译发现没有错误了,随后直接./a.out查看结果

printf("gintr is 0x%llx\n",*((long long unsigned *) & gintr));

在这里插入图片描述
实验时,这里犯了一个错误,一开始将代码修改为,由于gintr本身就是一个结构体,long long unsigned整数显然是不能赋值给结构体的

gintr=*((long long unsigned *) & gintr)
4. 掌握通用链表结构相关的C编程

首先查看一下ex4.c代码,首先定义了链表节点的结构体,包括一个list_entry对象以及一个数值num,主函数中,首先定义链表的头结点,经过初始化后,头结点的前驱与后继均为头结点自身,其数值为0.随后9次循环,不断调用list_add函数,逐步建立起链表。

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 ++) {
        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;
}

在list.h中,有list_entry双向链表数据结构的具体定义,结构体内有一个前驱指针和一个后继指针,随后是所有操作函数的说明,这里就不附上具体的代码了,在.c文件中可以直接使用

struct list_entry {
    struct list_entry *prev, *next;
};

typedef struct list_entry list_entry_t;

/* *
 * list_init - initialize a new entry
 * @elm:        new entry to be initialized
 * */
static inline void
list_init(list_entry_t *elm) {
    elm->prev = elm->next = elm;
}

为了实现基于此链表的数据对象的访问操作,可以在ex4.cd的基础上增加相关的查找、插入、删除、修改子函数

其中查找函数如下,加入了对索引范围的判断,打印该节点的数值,然后直接返回该节点,由于我们是对list_entry这个类进行操作,而它又是entry类的一个组成部分,所以在printf语句中要将指针类型进行强制转换,才能访问entry类中的num数据。

static inline list_entry_t * list_search(list_entry_t * head)
{
	int index,i;
	int length=10;
	printf("input the node's index:");
	scanf("%d",&index);
	while(index<=0 || index>length)
	{
		printf("index error,input the node's index:");
		scanf("%d",&index);
	}
	for (i=0;i<index;i++)
	{
		head = list_next(head);
	}
	printf("%d\n", ((struct entry *)head)->num);  //仿照原本主程序中的输出函数
	return head;
}

注意自己编写的子函数返回值类型要写成static inline list_entry_t *,而不能简单地写成struct list_entry_t *

/* *
 * list_next - get the next entry
 * @listelm:    the list head
 **/
static inline list_entry_t *
list_next(list_entry_t *listelm) {
    return listelm->next;
}

通过测试,发现运行成功
在这里插入图片描述
修改函数以及测试的结果如下

void list_change(list_entry_t * head)
{
	list_entry_t *p;
	p=list_search(head);
	printf("input num:");
	scanf("%d",&((struct entry *)p)->num);
}

在这里插入图片描述
插入函数以及测试结果

void list_insert(list_entry_t * head)
{
	list_entry_t *p;
	p=list_search(head);
	struct entry * e = (struct entry *)malloc(sizeof(struct entry));
	printf("input num:");
	scanf("%d",&e->num);
	list_add_after(p,&(e->node));
}

在这里插入图片描述
附上头文件中用到的具体函数的源代码

/* *
 * list_add_after - add a new entry
 * @listelm:    list head to add after
 * @elm:        new entry to be added
 *
 * Insert the new element @elm *after* the element @listelm which
 * is already in the list.
 * */
static inline void
list_add_after(list_entry_t *listelm, list_entry_t *elm) {
    __list_add(elm, listelm, listelm->next);
}
/* *
 * Insert a new entry between two known consecutive entries.
 *
 * This is only for internal list manipulation where we know
 * the prev/next entries already!
 * */
static inline void
__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) {
    prev->next = next->prev = elm;
    elm->next = next;
    elm->prev = prev;
}

删除函数,以及头文件中用到的具体函数的源代码

void list_delete(list_entry_t * head)
{
	list_entry_t *p;
	p=list_search(head);
	list_del(p);
}
/* *
 * list_del - deletes entry from list
 * @listelm:    the element to delete from the list
 *
 * Note: list_empty() on @listelm does not return true after this, the entry is
 * in an undefined state.
 * */
static inline void
list_del(list_entry_t *listelm) {
    __list_del(listelm->prev, listelm->next);
}
static inline void
__list_del(list_entry_t *prev, list_entry_t *next) {
    prev->next = next;
    next->prev = prev;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_43169112/article/details/104529767