C语言简单链表的反汇编

       从这篇文章开始我将自己的反汇编笔记分享给大家,有时间就会更新一些基础数据结构和算法的逆向,旨在如何用机器的模式去理解我们写的代码。需要的基础知识,比如汇编的一些知识我慢慢也会总结发出来。不过建议大家要有C语言的基础,看得懂基本语法。可以去看我偶像鹏哥的视频,我愿称之为C语言的YYDS,希望大家一起学习,一起进步。由于我的主旨是小白也能看懂,所以写作可能啰嗦了点,大家见谅。水平有限如果有错误欢迎大家指出。也欢迎大家交流。

首先我们创建一个简单的链表,实现插入和删除的基本操作,然后遍历打印出这个链表。下面是完整代码:

#include 
#include 

struct Node {
    int data;
    struct Node* next;
};

struct Node* head = NULL;

void Insert(int new_data) {
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    new_node->data = new_data;
    new_node->next = head;
    head = new_node;
}

void Delete(int key) {
    struct Node* temp = head;
    struct Node* prev = NULL;

    if (temp != NULL && temp->data == key) {
        head = temp->next;
        free(temp);
        return;
    }

    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }

    if (temp == NULL) return;

    prev->next = temp->next;
    free(temp);
}

void Display() {
    struct Node* temp = head;

    while (temp != NULL) {
        printf("%d ", temp->data);
        temp = temp->next;
    }

    printf("\n");
}

int main() {
    Insert(1);
    Insert(2);
    Insert(3);
    Insert(4);
    Insert(5);
    Display();
    Delete(3);
    Display();
    return 0;

大家可以尝试复制在VS中跑一下,我的环境是VS2022,哪个版本都一样不太影响。输出的样子如下图:

接下来我会带大家逐段逐段的分析。

 首先我们看主函数代码如下:

int main() {
    Insert(1);
    Insert(2);
    Insert(3);
    Insert(4);
    Insert(5);
    Display();
    Delete(3);
    Display();
    return 0;
}

这段代码很简单就是调用了5次Insert函数,然后调用一次Display函数,接着调用一次Delete函数,最后调用一次Display函数。相信小伙伴们看到函数名应该知道是干什么的,没错Insert就是插入的意思,Display是展示的意思也就是输出。那么Delete就是删除的意思。大致的三个函数实现我们链表的主要功能,插入删除和遍历输出。

接下来我们看看链表的结点是如何定义的代码如下:

struct Node {
    int data;
    struct Node* next;
};

这段代码非常简单定义了一个结构体,结构体的名字叫Node也就是结点的意思,里面有两个成员,一个int类型,一个struct Node*类型,相信学过C语言的小伙伴们不陌生,在C语言中int类型的变量占4个字节,指针类型的变量也是占四个字节,所以这个结点总共占用了8个字节的大小,这点非常重要,这在我们逆向分析的时候起到了非常关键的作用,所以大家在学习的过程中一定要重视基础,哪怕再简单细微的问题。小伙伴们也可以用关键字sizeof去测试一下是不是。如果用画图来理解这个结构体的话在内存中是这样子的:

C语言简单链表的反汇编_第1张图片

定义完结点后我们创建一个头结点,让他指向NULL,代码如下:

struct Node* head = NULL;

这就不解释了,这里我想说明的是这句代码和上面结点的定义,都是全局变量,也就是在程序运行中具有一整个生命周期,他们不存在栈上,也不在堆上,是放在静态存储区的,在程序开始执行时就分配了,希望大家理解这点,所以在VS中看反汇编一般是没有任何数据的如图:

C语言简单链表的反汇编_第2张图片

下面我们开始进入研究Insert函数代码如下:

void Insert(int new_data) {
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    new_node->data = new_data;
    new_node->next = head;
    head = new_node;

 大致讲解下这段函数的作用:接受一个int型参数,首先使用malloc函数在堆上申请一片空间,大小是一个结点的大小也就是8字节,创建成功会返回这个结点的首地址用new_node这个变量来接收。这里我没有做malloc函数的成功与否检查,在实际开发中还是做好这些边界检查,养成一个良好的习惯,我这里为了方便就省略了。接下来就是把传进来的参数赋值给结点的data域,让new_node这个结点的next指针指向head指向的位置,这里有点绕一会画图给大家解释。最后更新头结点指针,使之指向这个新结点,大家可以看出这其实是个头插法,插入的结点都是逆序输出的,所以插入1,2,3,4,5结点输出的时候会产生5,4,3,2,1大家可以好好体会下。画图理解:

 C语言简单链表的反汇编_第3张图片

第一次使用画图工具不太熟练,哈哈。应该能看的明白,我就不解释了。挺啰嗦的。好了,如果正向代码看懂了我们开始反汇编分析了。如下是VS反汇编出来的代码是debug版本的有时候我会出一个release版本的:C语言简单链表的反汇编_第4张图片

 这段反汇编其实大家现在可以跳过,如果有基础的明白这是在为一个函数开辟栈帧空间,以后我会单独写一个C语言函数栈帧空间的专题,带大家领悟透彻,也可以参考其他文章,网上应该挺多的。理解函数栈帧是非常有必要的。可以说理解函数栈帧打下了C语言大片江山,哈哈。可以先这么理解,这段反汇编的作用就是在内存中开辟了一块空间用来存放局部变量和传递的参数。有基础的可以忽略。接下来解析我们C语言的每一句代码在底层是怎么实现的。C语言简单链表的反汇编_第5张图片

 短短一句C语言背后要干这么多事,可想高级语言给我们带来的便利。那么为什么还需要汇编语言呢?因为汇编语言和机器码是一一对应的,所以我们分析汇编代码其实就是在理解机器到底是怎么运行我们的代码的。关于寄存器和常用的汇编指令我也会单独开专题讲解。看不懂的跳过就好啦。首先第一句是把寄存器esp的值复制给esi寄存器,作用是用于调试的,可以先不管,接下来才是重点,在调用函数的时候,我们需要将参数压栈,还记得malloc我们传入的参数是多少吗,前面分析过是8个字节,这里就对应上了,就是把参数压入栈中,在X32位的程序中,参数的传递是利用栈的,其实不单只有利用栈传递参数,还有利用寄存器传参的比如X64的程序。接下来就是一个call指令 后面跟着一个地址。这句汇编的意思是跳转到malloc函数所在的地址去执行malloc函数。我们理解到这里就可以了不用跟下去看里面长什么样子。接着是add esp,4 这句的意思是让esp+4 目的是弹出malloc的参数,cmp esi,esp是一种保护机制,用来保护栈没有被修改,下面的call 也是一个函数调用,这个函数也是保护栈的一种机制,确保程序的健壮,可以跳过。最后重点来了,将eax的值传递给new_node这个地址所在的内存中。我们能想到什么呢?没错,eax正是malloc的返回值,我们一个函数运行完,如果有返回值是不是需要用一个容器去接受它,那么malloc的返回值就保存在eax寄存器,事实上很多函数返回时都会把返回值放到eax寄存器中,理解了这一点在逆向的时候我们就能更快理解代码的含义。看,是不是很有趣很神奇!写了这么多感觉大家看的也很累,那么接下来的插入和遍历的函数反汇编分析我会放在后续的文章中分析。感谢大家的阅读!

 

你可能感兴趣的:(数据结构,链表,c语言)