首页
友链
关于

关于我在调试ICSPA堆区调节功能时把自己写的代码全部拷打了一遍甚至开始严查newlib正确性到头昏脑涨,最后却发现是自己用错了内存分配函数这一档子事

04 / 25 / 2024 (最后编辑于 04 / 25 / 2024)
预计阅读时间 28 分钟

本来不想写这篇博客的,想一口气把ICSPA做完了周末再写。

但是博客太久没更新了,等到周末估计又懒得写了,加之现在(写到这句话的时候一点零三)心情暴怒,遂作此博文。


首先有个大前提是我没用debugger,不然这bug至少能少调半天。

是这样的,在我用南大的框架实现内存管理的时候,有两个函数,一个叫new_page(int),一个叫pg_alloc(int)

前面一个是按输入的页数分配内存的,后面一个是按照输入的字节数分配内存的。

听到这里就已经很不对劲了,反正都是分页,为啥整这些花里胡哨弯弯绕绕的?

但是实际上后面那个是个本地函数,外部代码链接不上去,只是拿来传给虚存管理模块分配页表的。

所以你可以想象到的其他功能都应该使用前一个函数而不是后一个。

然后就要讲到我们今天的主角——堆区管理。

这ICSPA要求的堆区管理还是简化过的,只要用的堆空间超过已分配值,就多分配点,甚至不用管释放的事。

但是我的程序就在这个地方炸掉了。

只要一涉及到堆空间分配,一调malloc(),我的程序就会像撞见猫的老鼠一样,尖叫着跑向assertion failed。

但其实也不完全是,因为printf()屁事没有。

这时候已经晚上三点了,距离我踌躇满志地立下的DDL还有一天半。

虽然没看懂为啥,但是我还是感到不以为然,因为那天我刚调好了一个把程序加载进虚存的功能,还沾沾自喜地加了个映射对齐。这*玩意还能难倒我?

于是我雄心勃勃地上了床,准备第二天抖擞精神将bug快刀斩于马下,然后在今天(物理意义上的昨天)下午有条不紊地向导师汇报。躺在床上,我连汇报的词语都在想象中构思好了。


第二天起床去上AI导论课。课上也没讲啥太有用的东西,于是打开电脑和堆区管理死斗。

一开始我就把问题定位在了我的堆区管理函数上,这加加减减大于小于大于等于加一减一混着用的何成体统?稍微调了调,把分配的位置和syscall里管理好的位置对齐了一下,没什么变化。

本着从现象到本质的科普文写作结构,于是我在测试程序里一路打桩,能有读写的地方就assert(0)伺候,一把猛梭过后,发现是SDL新建Surface的malloc()挂掉了。

然后发现malloc没大事,倒是下面写了个用来把Surface初始化成黑色的memset(),我给它注释了程序就能往下跑,不注释就炸了。

注释了以后跑是跑起来了,但是会在后面另外一个读键盘信息的地方炸掉,然后在外设io的函数上加了一坨log,无果。

而且不注释的时候炸的位置恰好是下一个malloc(),估计是后面渲染Surface的函数里用的,一把猛梭以后,发现这个malloc分配了24字节内存,但是要了一整个page的内存

当时还想着说这玩意这么干好吗?后来想想它要要内存确实也只能一页一页要。

于是开搞,你要24字节要不到?那我自己要。我又在SDL_init()前后加了两大排malloc测试,从24字节到12345字节(瞎**打的)应有尽有不一而足,这下总能de出来了吧。

发现在init后面跟这的这个malloc还是失败。这时候突然发现我new_page函数里贴心的把新分配的页清空了,我一想这堆区也轮不着它清空啊,于是大刀阔斧给memset注释了。

这下好了,程序整个就跑不明白了,连访问的地址都成随机的了。

这*程序给我无来由的自信搞的荡然无存。

于是下午在劳动教育课上调程序加载器,这课也挺抽象的,别人都搞劳动教育实践,我们搞个劳动教育理论,老师讲的水平不如让我去扫大草坪实地感受一下劳动的重要性。

加载器调的抓耳挠腮,调的脑袋嗡嗡作响。后来想到可能是开始加载的时候给预留空间清零没搞对,一直调到思政课上。调出来后大喜过望,一阵骚动丑态如范进中举,甚至惊扰了邻座同学。

经常PUA的朋友们都知道,给一个人一点点希望然后又夺走远比一点希望都没有效果来的更好。冷静后转念一想,我这程序加载和堆区有个鸡毛关系?于是在惶恐中发现该爆的bug还是爆。然后我又把加载函数里加的修正加给了堆区分配函数,该爆的还是爆。

这下我的心态和我的程序一起彻底爆炸了。

凑巧电脑也没电了,想着这DDL肯定赶不上也不想赶了,眼神迷离望向手机和导师请了个假。

晚上意难平在宿舍暴打OSU!Mania,但是因为缺少睡眠加脑袋不舒服,手感十分冰凉,小打了几把就睡了。


今天(物理上的昨天)上午遂摆烂,跑到郎玩和舍友打乌蒙,由于是工作日没多少人,但是我又没课,遂美美霸机。

中午吃了自助寿喜烧,吃饱喝足回到宿舍躺尸。

晚上出门觅食,由于中午吃的太饱,觅的是第二天早饭,完成洗澡洗衣服等daily routine后,决定打开电脑再次与bug搏斗。

还是加各种log,加各种assert,不管用,我又摆弄起来上次发现memset以后会出错的地方。

实在找不出什么爆错的理由,因为这个错报的实在太诡异了。于是又回去干瞪堆区分配函数。

仔细想了想,我搞得那个映射对齐和riscv的映射方式有啥关系?然后把偏移量全设0了,结果发现我的得意之作除了可以给电脑城奸商一个推销内存条的机会以外毫无作用。

然后我就在怅惘中继续在测试程序里打桩,甚至一路打进newlib里去了,发现没什么改变。

然后不知道为啥想到自己试试写入下分配的内存试试:开始我先试着复刻一遍SDL_Init的操作,炸了。然后我把memset换成了大循环,又炸了。

这下好像有点明朗了,虽然还是没啥头绪,但是好歹爆了个新奇点的特征。

也许是冥冥中有天上哪个神仙看我蠢的看不下去了,一种难以抑制的冲动让我猛扭回去看堆区分配的代码,然后我看见了这个:

...
while (count < len) {
    void* page_entry = pg_alloc(1);
    ...
...

这玩意看着有点熟悉,又有点不对劲。

再扭回去一看,这**函数不是输入字节数进行分配的那个吗?

然后它给字节转换成页数,在4096的面前,再坚挺的1都得变成0。

但是页面分配那边调用这个函数是用的4096的整数倍……

你如果现在往上翻会发现,我刚刚不是说了,这玩意是本地函数来着,不能被外部调用吗?

巧就巧在,程序加载的函数在另一个文件里,但是这个堆区分配的函数和它在一个文件里……

我给它换成new_page(1),好了。

顿时感觉这一天半的努力就像一头驴在尝试用自己的双脚四足丈量眼前被板子挡住的狭窄的世界。

感觉想呼呼给自己来两巴掌。

累了,睡了。