博主头像
zzh

ワクワク

CSAPP-lab-随注

CSAPP-lab-随注

3、bomb-lab:

​ gdb的命令layout regs视图显示,好用捏;

​ 左移和右移的指令默认移位1位;

​ scanf函数的作用:把传入的字符串按照空格分割(rdi),最多读取(rsi,本身是字符串格式,用来规定读取的字符串的转化形式)个,读到的字符转为数字再压入栈中

​ 第五个谜题把输入的字符串保存在rdi保存的指针指向的地址中。里面放了金丝雀保护

​ move指令是把左边的放到右边,comp是右减左

第五个谜题把输入的字符串加工后与特定字符串“flyers”对比,相同即通过。谜题会把传入的每一个字符提取低四位,作为偏移量,在特定数组0x4024b0中查找。flyers对应的编码是:102, 108, 121, 101, 114, 115。数组中保存有相应编码的偏移量为9,15,14,5,7.要输入字符,因为掩码的提取以及英文字母编码范围的限制,可以在这些的基础上再加上96(16x6),得到传入字符串的编码:105,111,110,101,103,对应字符串为:ionefg

4、attck lab

通过注入字符串修改返回地址,注入代码或者修改程序运行结果

输入的字符串从栈顶注入,不断向上填充,有可能会覆盖掉返回的地址

注入的代码在栈中以小端序保存,但因为标准输入从栈顶注入,注入文本反而是正常(或者说大端序)的。而地址在注入文本上看是小端序的,实际填充入栈是正常的(大端序。)简单说,地址注入要倒过来,代码注入是正常顺序。举例说:要在栈中注入地址12 34 56 78,注入字符串应该是78 56 34 12.注入代码的指令编码是12 34,注入字符串是12 34

生成指令字节码的方式:(.s是汇编代码文件)

unix> gcc -c example.s

unix> objdump -d example.o > example.d

4010fa

touch3开启栈随机保护,所以在Test的栈帧里注入代码

push的指令后参数若不带上$,会被认为是地址,push的指令编码就不同,会把地址上的值推到栈上

如果使用不在farm中的工具可能会报段错误

第五个phase被工具限制增加了难度。其实本身难度并不太大,很多时间用来找指令编码。而且使用有一些指令,用gdb调试会发现没有问题,但是会爆段错误。也不清楚原因,就很抽象。可能是因为这道题实际上限制了能够使用的指令?

通过lea (%rdi,%rsi,1),%rax,利用栈指针和偏移量来得到 cookie 的地址。

5、archlab

前两个很简单,最后一个是开放性问题,懒得整了。不过环境不是很好配。而且这第四章也不是很重要,开摆开摆。

不过这一章和所学专业相关,如果有空,接触了Verlilog之后,可以尝试再看一遍仿真一下流水线处理器。毕竟不上手实践也很难真正理解。

6、perflab

第一个是旋转矩阵。使用循环展开,并且多设几个局部变量。但服务器性能不太行。分数都不咋地。一个相同的优化版本,别人跑出来的分数高出挺多的。矩阵分块的思想倒是从网上看到的。这好像和第六章缓存方面的内容有关。确实想不到。

牛魔的第二个硬是没看出来在干啥。看网上说才知道是让矩阵变平滑。笑嘻了。主要方法是分类讨论(顶点,边界,内部)和循环展开。难倒不难,就是太麻烦了。

7、cache lab

第一个部分是编写一个高速缓存模拟器。这部分几乎都是参考网上的资料。我个人基本上是无从下手。尤其是一些不知道的函数,还有一些莫名其妙的东西(接口?我也不知道该怎么叫)根本不知道怎么写。大部分都是参考网上的代码了。不过也反映了我本人的代码能力是不太行。参考](https://cloud.tencent.com/developer/article/1826689))

第二个部分是优化矩阵转置函数。在做这个的时候我有了几个想法:

1、首先是一个猜想:对于一个n乘n的矩阵,把它分为p乘p的矩阵块,那么要求高速内存能够一次存储n乘p个元素(依据在于矩阵实际上是线性的。要拿到块中的右下角元素,需要把前面的行都存储起来)。

2、分块后手动模拟一下转置函数,会发现由于两个矩阵共同映射到高速缓存中,即使做了优化1,刚刚放入缓存中的矩阵可能在下一刻就被替换掉。为了充分利用缓存,可以把最内层的循环展开。比如有八个元素放入缓存块中,就声明八个变量来存储这八个元素,一次利用完。哪怕之后被替换,这些元素也已经存放到了栈(或者寄8存器上)。

3、综合以上,我又有一个基于猜想之上的猜想:我们可以把矩阵二次分块。第一个大块p1*p1,p1是一个缓存块能够存放的元素数量。第二个小块p2乘p1,p2要满足猜想1(p2乘n个元素要能放入缓存中)。在处理小块内元素时通过方法2再次优化。

实际处理中会发现优化效果最好的是第一点:分块。第二点(循环展开)也有效果但其实并不太显著。

说真的这个lab比前面的难挺多的。

沃日搞完后突然发现我的服务器磁盘被塞满了。。感谢这个帖子](https://cloud.tencent.com/developer/article/2461408)),成功找出了罪魁祸首。。。差点以为我的服务器就被废掉了。。。。不知道怎么回事,lab里面的trace.tmp文件超级大。。。有点子逆天。。。

第七章:链接--概要:

本章没有实验,就简单写一下要点好了:

.c --> . i --> .s --> .o 依次是:C语言文件,中间文件,汇编语言文件,可重定位目标文件。

.a:归档文件(archive,静态库),把.o文件聚合起来归档;

.so:shared objects动态链接库,共享库。特点在于动态链接库在实际执行时才会加载和连接。

.h文件由预处理器处理,感觉可以理解为简单复制(当然实际上不是)。

目标文件有三种:可重定位目标文件,可执行目标文件,共享目标文件

连接器主要完成符号解析和重定位的作用。

库打桩机制:对一个需要打桩的目标函数,创建一个包装函数,原型与目标函数一致,通过某种机制,欺骗系统调用包装函数。(有点像一种劫持)。可以在连接,编译或执行时打桩。

8、shlab:

分享几个debug了蛮久的问题:

1、

void sigchld_handler(int sig) 
{
    int olderrno=errno;
    int child_pid;
    int status;
    while ((child_pid=waitpid(-1, &status, WNOHANG | WUNTRACED))){
        if (WIFEXITED(status)) {
        deletejob(jobs, child_pid);
        }
        else if (WIFSIGNALED(status)) {
        printf("process %d terminated by signal %d",child_pid,WTERMSIG(status));
        deletejob(jobs, child_pid);
        }
        else if (WIFSTOPPED(status)) {
        printf("process stopped by signal %d",WSTOPSIG(status));
        getjobpid(jobs, child_pid)->state=ST;
        }
    }
    errno=olderrno;   
    
    return;
}

使用上述函数进行测试会使tsh卡死。问题有2。首先我使用了while来循环回收子进程(其实是可以用while的),但与另外一个小细节结合起来就会有问题。那就是waitpid函数在信号集合为空时会返回-1.每次循环都会清理掉一个子进程,总会把集合清空,之后就不断返回-1,这样就形成了死循环。只需要做一个小改动即可:

 while ((child_pid=waitpid(-1, &status, WNOHANG | WUNTRACED))>0)

2、一个奇怪的bug。

void waitfg(pid_t pid)
{
     if(pid == 0) return; 
     while (getjobpid(jobs, pid)->state==FG) {
        sleep(1);
    }
    
    return;
  
}

使用上面的函数会导致tsh不输出对应的语句,如:

newuser@iZ7xv90wtya6z42b5ckbo5Z:~/CSAPP-Learn-Dir/shlab-handout$ ./sdriver.pl -t trace04.txt -s ./tsh -a "-p"
#
# trace04.txt - Run a background job.
#
tsh> ./myspin 1 &

但只要作出极小的修改(本人觉得在语义上没有区别),就可以通过:

void waitfg(pid_t pid)
{
     while (pid==fgpid(jobs)) {
        sleep(1);
    }
    
    return;
  
}

得到正确的结果:

newuser@iZ7xv90wtya6z42b5ckbo5Z:~/CSAPP-Learn-Dir/shlab-handout$ ./sdriver.pl -t trace05.txt -s ./tsh -a "-p"
#
# trace05.txt - Process jobs builtin command.
#
tsh> ./myspin 2 &
[1] (39597) ./myspin 2 &
tsh> ./myspin 3 &
[2] (39599) ./myspin 3 &
tsh> jobs
[1] (39597) Running ./myspin 2 &
[2] (39599) Running ./myspin 3 &newuser@iZ7xv90wtya6z42b5ckbo5Z:~/CSAPP-Learn-Dir/shlab-handout$ ./sdriver.pl -t trace04.txt -s ./tsh -a "-p"
#
# trace04.txt - Run a background job.
#
tsh> ./myspin 1 &
[1] (36676) ./myspin 1 &

该问题是在测试用例4和5的时候发现,用来测试background process。然而该函数只是用于foreground process的等待。本人学艺不精,没法发现原因。只能暂且搁置。

注:当父进程通过fork()创建子进程时,子进程会继承父进程的信号处理设置。但是,如果子进程随后调用了exec()族函数之一来执行另一个程序,那么所有的信号处理设置都会被重置为默认值。(这样可以避免陷入死循环)

3、

测试trace14时发现:Command not found 语句没有输出,其他都与参考文件相同。

以下是代码片段:

if (execve(argv[0], argv,NULL)<0) {
          printf("%s: Command not found\n",argv[0]);
          _exit(0);

但是用fflush刷新缓冲区即可输出:

if (execve(argv[0], argv,NULL)<0) {
          printf("%s: Command not found\n",argv[0]);
           fflush(stdout);
          _exit(0);

不使用tshdriver.pl,手动模拟,不用刷新缓冲区也能得到正确的输出。但是用驱动程序就不行。本人查询资料也没有找到原因。别人给出的代码也不需要刷新缓冲区就能直接输出。我运行别人的代码,可以正常输出,排除了机器的问题。很大可能是我程序的其他地方出现了问题,导致需要刷洗缓冲区。但这个bug也无伤大雅,直接搁置也无所谓。

还有一个地方有差不多的bug,如果不加上fflush,输出的语句顺序就会出问题:

void sigchld_handler(int sig) {
  int olderrno = errno;
  pid_t pid;
  int status;
  while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
    if (WIFEXITED(status)) {
      deletejob(jobs, pid);
    } else if (WIFSIGNALED(status)) {
      printf("Job [%d] (%d) terminated by signal 2\n", pid2jid(pid), pid);
      deletejob(jobs, pid); 

    } else if (WIFSTOPPED(status)) {
      printf("Job [%d] (%d) stopped by signal 20\n", pid2jid(pid), pid);
      fflush(stdout);//这里要刷新缓冲区
      struct job_t *curjob = getjobpid(jobs, pid);

      curjob->state = ST;
    }
  }
  errno = olderrno;
  
  return;
}

该bug在测试trace16时发现。我怀疑其实是出现了与并发相关的问题。错误并不在我贴出的代码段。但我也懒得修改了。

9、malloc_lab

​ 好难啊。。。本身写代码已经很困难了,debug也困难,时常爆段错误。用gdb调试异常退出的地方往往不在代码有问题处。找bug找到头爆炸。参考了这个网站。其中的realloc部分不太理解。对我这种鼠鼠来说还是太令人头秃了。。。

​ 也没精力列一些遇到的bug了,因为处处是bug...就这样吧。

​ 代码见链接

10、proxy_lab

该部分实验参考了这个网站](https://zhuanlan.zhihu.com/p/673844267))

分享遇到的问题:

part 1 问题

1、使用curl来进行调试的时候,发啊送的请求会附加请求报头,所以向tiny输送数据的时候需要把相同的请求报头过滤掉。

2、存放端口的字符串不能太小。我一开始设置成5个,导致读入数据的时候把path给覆盖了。

3、文件描述符关闭了两次。导致出错。

4、这个是参考网站也有的问题。网站中读入请求报头时这样使用如下函数:

sscanf(buf, "%s: %s", header->name, header->value);

但这种使用时不行的。加入当前的buf指针指向字符串:

Host: www.cmu.edu:97

那么读出来的name是:

Host::

value是空字符串。

应该按照下面的方法调用:

  sscanf(buf, "%[^:]: %s", header->header_name, header->header_data);

这样才能正确的读取。

调试主要结合gdb和curl,Telnet。

part2问题

同参考网站一样,要将文件中的 nop-server.py#!.....python 改成 #!....python3。不然通不过测试。其他就没有了。

part3 问题:

我运行的时候发现会爆段错误。使用Gdb调试的时候菜发现原来声明一个指针只会指向NULL。首先要用malloc分配好指向缓存的指针,再用calloc分配指向基本缓存单元的指针。具体来说,我一开始是这样写的:

CACHE* cache;
cache_init(cache,cache_size);

void cache_init(CACHE* cache,int n){
    cache->units=Calloc(n, sizeof(cache_unit));
    Sem_init(&cache->mutex, 0, 1);
    cache->unit_num=n;
}

但这样应为cache还没有初始化,init中的calloc就会爆段错误。

应该改成如下:

CACHE* cache;
cache=malloc(sizeof(CACHE));
cache_init(cache,cache_size);

void cache_init(CACHE* cache,int n){
    cache->units=Calloc(n, sizeof(cache_unit));
    Sem_init(&cache->mutex, 0, 1);
    cache->unit_num=n;
}

这样就没问题了。

总结

​ 好了,到此,大概三个月的CSAPP学习之旅就结束了。这本书的内容确实丰富。也让鼠鼠收获良多喵。一想到鼠鼠有好几个晚上研究lab抓耳挠腮就很乐。说真的,我感觉维护一个博客好累啊,整这些文档真麻烦,鼠鼠没啥精力仔细弄这些博客。
就这样结束吧,反正也没人看,就当做一个见证吧(乐)

CSAPP-lab-随注
https://zzhygs.cn/index.php/archives/55/
本文作者 zzh
发布时间 2025-03-24
许可协议 CC BY-NC-SA 4.0
发表新评论