C 是否可以恢复单个链表的头指针?

C 是否可以恢复单个链表的头指针?,c,linux,pointers,gcc,linked-list,C,Linux,Pointers,Gcc,Linked List,如果可以做出以下假设,是否可以恢复链表的头节点 链表是使用malloc创建的,可以从堆区域访问它 堆开始和结束地址可以从/proc/self/maps中找到(至少在Linux中是这样) 原始链接列表中至少有一个节点是可访问的 指向上一个节点的指针将在堆的某个地方找到 它可以递归搜索,直到找到真正的头部 为了更好地说明这一点,请使用以下程序,在默认配置下,至少可以在Ubuntu/WSL下使用gcc成功编译该程序 节目 #include <stdio.h> #include <st

如果可以做出以下假设,是否可以恢复链表的头节点

  • 链表是使用malloc创建的,可以从堆区域访问它
  • 堆开始和结束地址可以从/proc/self/maps中找到(至少在Linux中是这样)
  • 原始链接列表中至少有一个节点是可访问的
  • 指向上一个节点的指针将在堆的某个地方找到
  • 它可以递归搜索,直到找到真正的头部
  • 为了更好地说明这一点,请使用以下程序,在默认配置下,至少可以在Ubuntu/WSL下使用gcc成功编译该程序

    节目

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    
    typedef struct node
    {
        int val;
        struct node *next;
    } node_t;
    node_t *head = NULL;
    unsigned long start_address = 0;
    unsigned long end_address = 0;
    
    node_t *getLastNode()
    {
        node_t *iter = head;
        for (; iter->next != NULL; iter = iter->next)
            ;
        return iter;
    }
    void addToLinkedList(int value)
    {
        node_t *data = malloc(sizeof(node_t));
        data->val = value;
        data->next = NULL;
    
        if (head == NULL)
            head = data;
        else
            getLastNode()->next = data;
    }
    
    void createLinkedList()
    {
        // Add 10 nodes to the linked list
        int start_val = 0x10101010;
        for (int i = 1; i <= 10; i++)
            addToLinkedList(start_val * i);
    }
    
    void printLinkedList()
    {
        printf("Head pointer of Linked List : %p\n", head);
        for (node_t *iter = head; iter != NULL; iter = iter->next)
            printf("%p -> value = %X, next = %p \n", iter, iter->val, iter->next);
        printf("\n");
    }
    
    void resetHeadPtr()
    {
        // Lets make head point to the last node
        head = getLastNode();
    }
    
    void findHeapBoundary()
    {
        // Code inspired from https://unix.stackexchange.com/a/251769/152334
        char mapsFilename[1024];
        char line[256];
    
        char area[1024];
        sprintf(mapsFilename, "/proc/%d/maps", getpid());
        FILE *pMapsFile = fopen(mapsFilename, "r");
    
        while (fgets(line, 256, pMapsFile) != NULL)
        {
            // Dirty hack to get the heap start and end address
            sscanf(line, "%08lx-%08lx%*[^[]%s\n", &start_address, &end_address, area);
            if (strcmp(area, "[heap]") == 0)
                break;
        }
        fclose(pMapsFile);
        printf("Heap memory start address : %p\n", (int *)start_address);
        printf("Heap memory end address   : %p\n", (int *)end_address);
    }
    
    node_t *findPointerInMemory()
    {
        for (int *ptr = (int *)start_address; ptr < (int *)(end_address - sizeof(node_t)); ptr++)
        {
            if (((node_t *)ptr)->next == head)
                return (node_t *)ptr;
        }
        return NULL;
    }
    
    void recoverHeadPtr()
    {
        node_t *ptr = findPointerInMemory();
        if (ptr == NULL)
        {
            printf("Cannot find %p in heap memory\nStopping Search\n\n", head);
            return;
        }
        printf("Found %p at %p\n", head, ptr);
        head = ptr;
        recoverHeadPtr();
    }
    
    int main(int argc, char const *argv[])
    {
        createLinkedList();
        printf("Original Linked List Contents\n*****************************\n");
        printLinkedList();
    
        resetHeadPtr();
        printf("Linked List Contents after reset\n********************************\n");
        printLinkedList();
    
        findHeapBoundary();
        recoverHeadPtr();
    
        printf("Recovered Linked List Contents\n******************************\n");
        printLinkedList();
        return 0;
    }
    
    背景

    我的一位朋友在一次采访中被问到以下问题。“如果给定了最后一个节点,您能否找到单个链表的头指针?”。他回答“不”,尽管面试官对答案并不完全满意,但他还是得到了这份工作。这让我思考,是否有可能这样做

    所以,真正的问题是。

  • 这种方法正确吗
  • 我们需要在其他内存段中搜索吗
  • 这种方法也可以推广到Windows上吗
  • ASLR会对这种方法产生任何影响吗

  • 在标准C中,这是不可能的

    除了通过malloc提供的指针(或这些指针的副本等)访问堆之外,还将获得未定义的行为


    但是,如果您了解分配器,并且能够了解已分配内存块的结构,则可以找到候选列表节点。其中一个是头部。

    在标准C中,这是不可能的

    除了通过malloc提供的指针(或这些指针的副本等)访问堆之外,还将获得未定义的行为


    但是,如果您了解分配器,并且能够了解已分配内存块的结构,则可以找到候选列表节点。其中一个是头部。

    正确答案应该是:

    对于一个糟糕的设计来说,不干净和试图这样做是一个危险的解决办法。
    如果您确实尝试搜索内存中任何内容的地址(如果该地址是有问题的指针,则不能保证只包含该地址),那么您可能会找到任何似乎意外包含与有问题的地址类似的数字的内存块。
    如果你继续使用它,假设它是指针,那么你会引发各种各样的问题。
    如果您反复这样做,以向后浏览单个链接列表,那么您实际上可以保证找到至少一个无意义的内容

    简言之,没有


    简单的“否”太短,可能会让考官因此皱眉。

    正确答案应该是:

    对于一个糟糕的设计来说,不干净和试图这样做是一个危险的解决办法。
    如果您确实尝试搜索内存中任何内容的地址(如果该地址是有问题的指针,则不能保证只包含该地址),那么您可能会找到任何似乎意外包含与有问题的地址类似的数字的内存块。
    如果你继续使用它,假设它是指针,那么你会引发各种各样的问题。
    如果您反复这样做,以向后浏览单个链接列表,那么您实际上可以保证找到至少一个无意义的内容

    简言之,没有


    简单的“否”太短,可能会让考官因此皱眉。

    可能吗?对实用的?不会。它会破坏C规范中规定的许多规则。使用
    next
    值查找内存中的上一个节点,会对应用程序产生一些误报。评估由
    malloc()
    管理的“私有”堆组织,并且它可能是可用的。(像Valgrind这样的工具肯定是这样做的。)除此之外,我也会投反对票。该代码不可靠(除其他问题外),因为您发现一个恰好与地址相同位的值这一事实意味着很少。它可以是一个整数或一个恰好具有正确位模式的浮点数。或者,更可能的情况是,它可能是指向同一地址的实际指针,只是指针位于分配给对象的内存中,该对象与它用来指向的对象一起被释放。如果您了解malloc使用的元数据,并且只搜索sizeof(node_t)的块,那么它的范围会缩小得多字节和仅分配的块。最后一部分将非常重要,因为从列表的开头删除一个元素会将旧节点留在内存中,从而导致错误命中。可能吗?对实用的?不会。它会破坏C规范中规定的许多规则。使用
    next
    值查找内存中的上一个节点,会对应用程序产生一些误报。评估由
    malloc()
    管理的“私有”堆组织,并且它可能是可用的。(像Valgrind这样的工具肯定是这样做的。)除此之外,我也会投反对票。该代码不可靠(除其他问题外),因为您发现一个恰好与地址相同位的值这一事实意味着很少。它可以是一个整数或一个恰好具有正确位模式的浮点数。或者,更可能的情况是,它可能是指向同一地址的实际指针,只是指针位于分配给对象的内存中,该对象与它用来指向的对象一起被释放。如果您了解malloc使用的元数据,并且只搜索sizeof(node_t)的块,那么它的范围会缩小得多字节和仅分配的块。最后一部分将非常重要,因为
    Original Linked List Contents
    *****************************
    Head pointer of Linked List : 0x1db6010
    0x1db6010 -> value = 10101010, next = 0x1db6030
    0x1db6030 -> value = 20202020, next = 0x1db6050
    0x1db6050 -> value = 30303030, next = 0x1db6070
    0x1db6070 -> value = 40404040, next = 0x1db6090
    0x1db6090 -> value = 50505050, next = 0x1db60b0
    0x1db60b0 -> value = 60606060, next = 0x1db60d0
    0x1db60d0 -> value = 70707070, next = 0x1db60f0
    0x1db60f0 -> value = 80808080, next = 0x1db6110
    0x1db6110 -> value = 90909090, next = 0x1db6130
    0x1db6130 -> value = A0A0A0A0, next = (nil)
    
    Linked List Contents after reset
    ********************************
    Head pointer of Linked List : 0x1db6130
    0x1db6130 -> value = A0A0A0A0, next = (nil)
    
    Heap memory start address : 0x1db6000
    Heap memory end address   : 0x1dd7000
    Found 0x1db6130 at 0x1db6110
    Found 0x1db6110 at 0x1db60f0
    Found 0x1db60f0 at 0x1db60d0
    Found 0x1db60d0 at 0x1db60b0
    Found 0x1db60b0 at 0x1db6090
    Found 0x1db6090 at 0x1db6070
    Found 0x1db6070 at 0x1db6050
    Found 0x1db6050 at 0x1db6030
    Found 0x1db6030 at 0x1db6010
    Cannot find 0x1db6010 in heap memory
    Stopping Search
    
    Recovered Linked List Contents
    ******************************
    Head pointer of Linked List : 0x1db6010
    0x1db6010 -> value = 10101010, next = 0x1db6030
    0x1db6030 -> value = 20202020, next = 0x1db6050
    0x1db6050 -> value = 30303030, next = 0x1db6070
    0x1db6070 -> value = 40404040, next = 0x1db6090
    0x1db6090 -> value = 50505050, next = 0x1db60b0
    0x1db60b0 -> value = 60606060, next = 0x1db60d0
    0x1db60d0 -> value = 70707070, next = 0x1db60f0
    0x1db60f0 -> value = 80808080, next = 0x1db6110
    0x1db6110 -> value = 90909090, next = 0x1db6130
    0x1db6130 -> value = A0A0A0A0, next = (nil)