You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

INI解析器多section场景触发segmentation fault崩溃排查

INI解析器段错误故障排查

故障现象

开发INI解析器,目标是将所有section存入数组供后续调用,目前SECTION_OPEN逻辑分支触发故障:调用ini_init()函数时,函数首行执行内存分配操作就触发Segmentation Fault崩溃。已尝试以下排查手段:

  • 为malloc调用分配超过100000字节的多种不同大小内存空间,程序仍然触发崩溃
  • 采用逐段注释代码后逐步取消注释的方式,定位触发崩溃的具体代码位置

测试文件

测试用test.ini内容如下:

; This is a valid comment
; Global variables work
var = 1
var2 = 2
var3 = 3

[Test1] ; Section
name = Primitive
organization = PrimOS
test = var
another = test
once_again=another_test

; Will check to see if variables work as intended
[.Test2] ; Subsection
; This variable will crash
another_organization = %name%

; This test will crash
[Test3] ; Section
another_name = "This is me"

关联代码

ini.h头文件

#ifndef SEAWARE_INI_H
#define SEAWARE_INI_H

#define MAX_HOLDING_CELL 255
#define MAX_LINE_LENGTH 255

// Syntax
#define QUOTES '"'
#define SECTION_OPEN '['
#define SUBSECTION '.'
#define SECTION_CLOSE ']'
#define VARIABLE_CALL '%'
#define COMMENT ';'
#define EQUALS '='
#define EQUALS_PTR "="

// Only one variableType for each variable
struct variable {
    char * variableName;
    char * value;
};

// The magic...
struct INI {
    // If NULL, you are global
    char * sectionName;
    // Like [.Test] or [Test1.Test]
    struct INI ** subSections;

    // Self explanitory
    struct variable ** variables;
    int subSectionsPtr;
    int variablesPtr;
    struct INI * nextSection;
    struct INI * prevSection;
};

struct INI * ini_init();
struct INI * interpret(struct INI * ini, char * filePath);

#endif

ini.c实现文件

#include <ini.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>

// Global functions
// Sets up new struct INI * variable
struct INI * ini_init() {
    // Crashes
    struct INI * newINI = malloc(sizeof(struct INI));

    newINI->variablesPtr = 0;
    newINI->subSectionsPtr = 0;
    newINI->variables = malloc(sizeof(struct variable*)*(newINI->variablesPtr+1));
    newINI->subSections = malloc(sizeof(struct INI *)*(newINI->subSectionsPtr+1));

    *newINI->variables = NULL;
    *newINI->subSections = NULL;
    newINI->sectionName = NULL;
    newINI->nextSection = NULL;
    newINI->prevSection = NULL;

    return newINI;
}

// Interprets an INI file, with syntax defined in ../include/ini.h
struct INI * interpret(struct INI * ini, char * filePath) {
    // Loop Variables    
    char line[MAX_LINE_LENGTH];
    FILE * iniFile = fopen(filePath, "r");
    bool isSubsection = false;

    // Read line by line
    while(fgets(line, sizeof(line), iniFile)) {
        bool isComment = false;
        bool isQuoted = false;

        // A holding cell for all the bad defaults out there, stay safe out there...
        char holdingCell[MAX_HOLDING_CELL];
        int holdingCellPtr = 0;

        // For each character in line
        for (int i = 0; i < strlen(line); i++) {
            printf("%c", line[i]);
            // Prevents memory leaks in holdingCell
            holdingCell[holdingCellPtr] = '\0';

            // Interpret
            switch(line[i]) {

                // Move into section
                case SECTION_OPEN: {
                    // If you are in a subsection, get out of it
                    if (isSubsection == true) {
                        ini = ini->prevSection;
                        isSubsection = false;
                    }

                    struct INI * newSection = ini_init();
                    if (line[(i+1)]==SUBSECTION) {
                        isSubsection = true;
                        // Reallocate size of subSections array
                        ini->subSections =  realloc(ini->subSections, sizeof(struct INI *) * (ini->subSectionsPtr+1));
                        
                        // Set new subsection and enter it
                        newSection->prevSection = ini;
                        ini->subSections[ini->subSectionsPtr] = newSection;
                        ini->subSectionsPtr++;
                        ini = ini->subSections[ini->subSectionsPtr];
                        i++;
                    }
                    else {
                        // Create new section and go into it
                        newSection->prevSection = ini;
                        ini->nextSection = newSection;
                        ini = ini->nextSection;
                        ini->nextSection = NULL;
                    }
                    break;
                }

                // Set sectionName
                case SECTION_CLOSE: {
                    ini->sectionName = malloc(sizeof(char) * strlen(holdingCell));
                    strcpy(ini->sectionName, holdingCell);
                    break;
                }

                // Creates new variable and assigns name and value
                case EQUALS: {
                    isQuoted = true;
                    struct variable * newVar = malloc(sizeof(struct variable)*(ini->variablesPtr+6));
                    char * value = strtok(line, EQUALS_PTR);

                    // Set variableName
                    newVar->variableName = malloc(sizeof(char)*strlen(value));
                    strcpy(newVar->variableName, value);

                    // After EQUALS
                    value = strtok(NULL, EQUALS_PTR);

                    // Set value
                    newVar->value = malloc(sizeof(char)*strlen(value));
                    strcpy(newVar->value, value);
                    
                    printf("%s", value);

                    // Add variable to ini field
                    ini->variables = realloc(ini->variables, sizeof(struct variable*)*(ini->variablesPtr+1));
                    ini->variables[ini->variablesPtr] = newVar;
                    ini->variablesPtr++;
                    break;
                }
                
                // Skips to new line
                case COMMENT: {
                    isComment = true;
                    break;
                }
                
                // Gets names and values
                default: {
                    holdingCell[holdingCellPtr] = line[i];
                    holdingCellPtr++;
                    break;
                }
            }
            if (isComment == true || isQuoted == true)
                break;
        }
    }

    // Stop reading file
    fclose(iniFile);

    // Loop back to beginning
    while(ini->prevSection != NULL) {
        ini = ini->prevSection;
    }

    // You get what you get, stop throwing a fit
    return ini;
}

根因定位

ini_init()里malloc直接崩,根本不是malloc本身的问题,是之前的代码已经把堆内存的元数据踩坏了,malloc遍历堆空闲链表的时候访问到非法地址才触发崩溃,具体问题点:

  1. 核心越界:子节跳转逻辑写错索引
    处理子section的三行代码顺序逻辑错误:
    ini->subSections[ini->subSectionsPtr] = newSection;
    ini->subSectionsPtr++;
    ini = ini->subSections[ini->subSectionsPtr];
    
    给subSectionsPtr自增后,直接取自增后索引的指针赋值给ini,这个位置的内存根本没初始化,是随机野值。后续所有基于这个野指针的内存操作都是非法写,直接破坏堆结构。
    正确写法应该是跳转到刚存入的新节点,也就是自增前的索引位置:ini = ini->subSections[ini->subSectionsPtr - 1];
  2. 所有字符串分配没给结束符留空间
    不管是section名、变量名还是变量值,分配内存时都只分配了strlen()返回的长度,没有额外+1字节存C语言字符串必需的\0结束符。strcpy执行时会把结束符写到分配的内存块外,属于堆越界写,同样会破坏堆元数据。
  3. 缺少基础校验逻辑
    • fopen打开文件后没判断返回值是否为NULL,文件打开失败时直接传给fgets会触发空指针访问
    • 所有malloc/realloc的返回值没做判空处理,内存分配失败时直接访问会崩溃
    • 写holdingCell缓冲区时没做边界判断,行内容过长会直接越界写栈空间
  4. 冗余错误的内存分配
    创建variable结构体时写了malloc(sizeof(struct variable)*(ini->variablesPtr+6)),实际只需要存1个variable结构体,分配sizeof(struct variable)即可,多余分配的内存虽然不会直接触发崩溃,但属于逻辑错误,后续操作不当很容易踩内存。

修复方向

  • 修正子section跳转的索引错误,进入新创建的子节时取subSectionsPtr-1位置的指针
  • 所有字符串内存分配长度统一+1,预留\0结束符位置
  • 补充文件打开、内存分配的返回值校验,出错时直接返回并打印错误信息
  • 给holdingCell增加写入边界判断,写入前检查指针是否超过缓冲区长度,避免栈溢出

内容的提问来源于stack exchange,提问作者user18348624

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 06:42:37