|||
最新魔改PGSQL--- 鲨鱼DB的客户端程序,为其增加SHOW 命令
在主程序MAINLOOP.C里面有个CHAR* LINE 字符串指针,该指针读取标准输出里面获得用户输入的命令
/* Now we can fetch a line */
line = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);
line = process_show(line); //魔改处
第2行把字符指针传入处理函数,进行替换,也可以把SQL语句用简单的SHOW COMMAND VALUE格式 简单格式替代
if (strlen(pValues) > 1) // 命令后面没有其它字符
{
int NewLine_size = 90 + value_size;
char *NewLine = (char *)malloc(NewLine_size * sizeof(char));
memset(NewLine, '\0', NewLine_size);
strcpy(NewLine, "SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%");
strcat(NewLine, pValues);
strcat(NewLine, "%';");
free(pValues); pValues = NULL;
free(pShow); pShow = NULL;
return NewLine;
}
这段代码重点是 NEWLINE动态分配,并返回给外面函数. 字C符指针接收!
原本是这样改的,起先简单的MALLOC分配不多的时候,能正常执行下去不报错.如果是这样长SQL语句替换的话,
psql_scan_setup和psql_scan两个函数里面的内存分配和重新分配就报错.
/* * Parse line, looking for command separators. */
psql_scan_setup(scan_state, line, strlen(line), pset.encoding, standard_strings() );
success = true; line_saved_in_history = false;
while (success || !die_on_error)
{ PsqlScanResult scan_result;
promptStatus_t prompt_tmp = prompt_status;
size_t pos_in_query;
char *tmp_line;
pos_in_query = query_buf->len;
scan_result = psql_scan(scan_state, query_buf, &prompt_tmp);
prompt_status = prompt_tmp;
}
报错是scan_state里面的某个成员的地址会跟LINE新地址发生了冲突.
意思是成员的地址覆盖LINE的新地址. 折腾一休也没有找到为什么会覆盖.
所以只好修改外面调用函数,先自定义个新字符指针来接受用户输入的命令
然后传给处理函数,处理函数照旧把新内存地址给LINE. 然后FREE临时字符指针内存.这样上面两个函数分配和重新分配内存就不报错了
/* Now we can fetch a line */
char *pTempLine = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);
line = process_show(pTempLine);
free(pTempLine);
pTempLine = NULL;
结果如下面能执行SQL 不过还是报错
SELECT n.nspname AS " Schema ",c.relname AS " Table ",t.tgname AS " Trigger Name ",pg_catalog.pg_get_triggerdef(t.oid) AS " Trigger Definition "
FROM pg_catalog.pg_trigger t JOIN pg_catalog.pg_class c ON c.oid = t.tgrelid
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE NOT t.tgisinternal AND n.nspname <> 'pg_catalog'
AND n.nspname <> 'information_schema'
ORDER BY n.nspname, c.relname, t.tgname;
psql_scan_finish(scan_state);
if(line != NULL)
{
printf("%s\n",line);
free(line);
}
居然释放前地址没有变化,还能打印出内容,就是释放就报错.这就令人痛苦.
错误和理论矛盾啊
AI 这样说
常见的导致“double free or corruption”错误的原因:双重释放:你可能在某个地方多次调用了 free(line),
即两次或更多次释放了同一块内存。这会导致内存管理器试图释放已经释放的内存,从而触发错误。内存越界:如果你通过某种方式修改了指向已释放内存的指针,可能会导致重复释放,或者出现内存损坏的问题。悬挂指针:如果在释放内存后,你没有将指针设为 NULL,在之后的代码中使用该指针会导致不确定的行为(例如,再次尝试释放同一块内存)。推荐在 free() 之后将指针置为 NULL,以防止悬挂指针问题。
好像没有一条符合预期
<strong>使用内存检测工具 valgrind
</strong>
[shark@sharkdb=>sharkdb17]$valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --log-file=Valgrind.log pgsql -d testdb
pgsql (17.1)
Type "help" for help.
SHARKSQL> show trigs
+----------+---------+----------------+----------------------+
| Schema | Table | Trigger Name | Trigger Definition |
+----------+---------+----------------+----------------------+
+----------+---------+----------------+----------------------+
(0 rows)
Time: 138.350 ms
SHARKSQL> quit
日志非常多 PG17.1咋这样呢! 我们找到 free()相关的日志
==65525== Invalid free() / delete / delete[] / realloc()
==65525== at 0x4C2E338: free (vg_replace_malloc.c:989)
==65525== by 0x424853: MainLoop (mainloop.c:343)
==65525== by 0x42EEB5: main (startup.c:462)
==65525== Address 0x7f7e770 is 0 bytes inside a block of size 5 free'd
==65525== at 0x4C2E338: free (vg_replace_malloc.c:989)
==65525== by 0x424472: MainLoop (mainloop.c:173)
==65525== by 0x42EEB5: main (startup.c:462)
==65525== Block was alloc'd at
==65525== at 0x4C2B37A: malloc (vg_replace_malloc.c:446)
==65525== by 0x50CCB28: xmalloc (in /usr/lib64/libreadline.so.6.2)
==65525== by 0x50B32FC: readline_internal_teardown (in /usr/lib64/libreadline.so.6.2)
==65525== by 0x50B40E1: readline (in /usr/lib64/libreadline.so.6.2)
==65525== by 0x423194: gets_interactive (input.c:91)
==65525== by 0x424452: MainLoop (mainloop.c:170)
==65525== by 0x42EEB5: main (startup.c:462)
MAINLOOP 343行和173.从上往下看INVALID FREE()343行FREE和下面的Address 0x7f7e770 173行的FREE冲突了,
分配内存是MALLOC 170行的函数,以及函数里的调用INPUT.C:91行. 其实跟MYSQL死锁差不多,只是简单些.
#ifndef WIN32
puts(_("Use control-D to quit."));
#else puts(_("Use control-C to quit."));
#endif
}
else { /* exit app */ free(line); fflush(stdout); successResult = EXIT_SUCCESS; break; }
这是退出时候FREE的,而原始分配内存代码,目前改成主函数释放掉,下面是173行释放
/* Now we can fetch a line */
char *pTempLine = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);
//line = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);
line = process_show(pTempLine);
free(pTempLine);
pTempLine = NULL;
}
用临时字符指针获得gets_interactive函数返回的内存.传给处理函数后就释放掉.而line是处理函数返回的内存.下面是585行的,使用VSCODE调试就这里报错
psql_scan_finish(scan_state);
if(line != NULL) { // printf("%s\n",line); free(line); line=NULL; }
其它的FREE(LINE)有6处,且这6处原本是PG17自带的,并非魔改的.因此这6处位置都是正常的.
这就让人奇怪,难道某个地方记录了第一次分配内存的地址.释放的时候就释放第一次分配的内存地址? 第二次分配的内存地址不释放? 通俗的说我给渣男他介绍了两个女朋友. 第一个女朋友姓张,第二个女朋友姓熊.我跟渣男说 "你还是分手吧!" 结果渣男跑去见了第一个女朋友张仙女,跟她说"我们还是分手吧!" 应该跑去见第2个女朋友熊小姐说分手.
PG原本没有错,假如我不加如PROCESS_SHOW函数.而且PROCESS_SHOW函数其它SHOW命令,尤其那种比较短的,就不会报错.
if (strcmp(arry_command, "databases") == 0) // 如果命令是databases
{
if (command_size == LineLen) // 命令后面没有其它字符
{
char *NewLine = (char *)malloc(6);
memset(NewLine, '\0', 6);
strcpy(NewLine, "\\list");
free(pShow);
pShow = NULL;
return NewLine;
}
}
像这样把原本的LINE内存不管,重新分配6个字节内存返回给LINE. 后续就不会出现DOUBLE FREE. 如果分配字节太大了的话,会被后面的内存分配所覆盖.
现在使用长的SQL要分配420个字节,就报错重复释放内存的怪兽!
恢复原样的话
line = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf);
line = process_show(line); 就报此错误
corrupted size vs. prev_size: 0x00000000011dc150
AI回答:
是 C 语言程序在使用动态内存分配时,由于内存管理出现错误而引发的错误消息,
通常与内存泄漏、越界访问或者双重释放等问题有关。它表明程序在释放内存时,
检测到内存管理系统中的元数据(如内存块大小)被篡改或损坏。
错误原因
在 C 语言中,动态内存的分配是由 malloc、calloc、realloc 等函数管理的,内存块通常会存储一些元数据,包括内存块的大小信息。当你释放内存时,内存管理系统会检查这些元数据来确保内存的正确释放。如果内存块的大小信息在某种操作中被意外篡改,可能会导致这种错误。corrupted size vs. prev_size 错误通常是以下几种情况造成的:
越界写入(Buffer Overflow):
当你在一个动态分配的内存块中写入超出分配范围的内容时,可能会修改内存块前后的元数据,导致内存管理系统无法正确识别块的大小。
双重释放(Double Free):
如果你尝试释放一个已经释放过的内存块,或者内存块的释放顺序不正确,内存管理系统可能会发现内存块的元数据不一致,从而触发这个错误。
未初始化或已销毁的指针:
如果你使用一个未初始化或已经销毁的指针来访问内存,可能会导致内存管理系统检测到内存结构损坏。
内存分配错误:
内存分配时发生了某些不可预见的错误,导致内存块的元数据被损坏,触发检测机制。
也就是说我窜改了LINE 字符指针的元数据, 这就有意思了!
</code></pre><pre style="text-align: left;"><code>
写个简单的C语言验证下
#include
#include
#include
char * process(char * pline)
{
char* new_line=malloc(100);
strcpy(new_line, "SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%");
printf("new_line size:%d\n",strlen(new_line));
return new_line;
}
int main()
{
char *line;line= malloc(8);
line=process(line);
if(line !=NULL)
{
printf("%s\n",line);
free(line) ;
}
}
居然没有报错了
[shark@sharkdb=>sharkdb17]$./main.exe
new_line size:84
SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
</code></pre><pre style="text-align: left;"><code>
这样子也不报错:
#include
#include
#include
char * process(char * pline)
{
char* new_line=malloc(100);
memset(new_line,'\0',100);
strcpy(new_line, "SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%");
printf("new_line size:%d\n",strlen(new_line)); return new_line;
}
int main()
{
char *line;line= malloc(8);
memset(line,'\0',8);
strcpy(line,"I LOVE DB");
printf("old_line:%s\n",line);
line=process(line);
if(line !=NULL)
{
printf("%s\n",line);
free(line);
}
}
</code></pre><pre style="text-align: left;"><code>
[shark@sharkdb=>sharkdb17]$./main.exe
old_line:I LOVE DB
new_line size:84
SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
</code></pre><pre style="text-align: left;"><code>模拟DOUBLE FREE场景
#include #include #include
char * process(char * pline)
{ char* new_line=malloc(100);
memset(new_line,'\0',100);
strcpy(new_line, "SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%");
printf("new_line size:%d\n",strlen(new_line));
return new_line;}
int main()
{char *line;char *tline;
tline= malloc(8);
memset(tline,'\0',8);
strcpy(tline,"I LOVE DB");
printf("old_line:%s\n",tline);
line=process(tline);free(tline);
tline=NULL;
if(line !=NULL)
{ printf("%s\n",line); free(line);})
也不报错!
[shark@sharkdb=>sharkdb17]$./main.exe
old_line:I LOVE DB
new_line size:84
SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
自然是存在内存泄漏的.
valgrind --tool=memcheck --leak-check=yes --show-reachable=yes ./main.exe
直接打印到屏幕上,先看最后总结部分
old_line:I LOVE DB
new_line size:84
SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
==71981==
==71981== HEAP SUMMARY:
==71981== in use at exit: 0 bytes in 0 blocks
==71981== total heap usage: 2 allocs, 2 frees, 108 bytes allocated
==71981==
==71981== All heap blocks were freed -- no leaks are possible
==71981==
==71981== For lists of detected and suppressed errors, rerun with: -s
==71981== ERROR SUMMARY: 6 errors from 4 contexts (suppressed: 0 from 0)
堆分配两次,释放了两次 合计分配了108个字节,内存分配符合程序预期.和逻辑
</code></pre><pre style="text-align: left;"><code>第1个错,无效写入2个字节,分别在19行和21行
==71981== Memcheck, a memory error detector
==71981== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==71981== Using Valgrind-3.24.0 and LibVEX; rerun with -h for copyright info
==71981== Command: ./main.exe
==71981==
==71981== Invalid write of size 2
==71981== at 0x4007C1: main (main.c:21)
==71981== Address 0x520d048 is 0 bytes after a block of size 8 alloc'd
==71981== at 0x4C2B37A: malloc (vg_replace_malloc.c:446)
==71981== by 0x400795: main (main.c:19)
分配8个字节写入9个字母,外加\0,
19 tline= malloc(8);
21 strcpy(tline,"I LOVE DB");
</code></pre><pre style="text-align: left;"><code>第2个错误 无效读入1个字节
==71981== Invalid read of size 1
==71981== at 0x4E8C079: vfprintf (in /usr/lib64/libc-2.17.so)
==71981== by 0x4E924E8: printf (in /usr/lib64/libc-2.17.so)
==71981== by 0x4007DC: main (main.c:22)
==71981== Address 0x520d048 is 0 bytes after a block of size 8 alloc'd
==71981== at 0x4C2B37A: malloc (vg_replace_malloc.c:446)
==71981== by 0x400795: main (main.c:19)
</code></pre><ul><li></li></ul><pre><code >22 printf("old_line:%s\n",tline);
</code></pre><pre style="text-align: left;"><code>运行正常 9个全打印出来了
[shark@sharkdb=>sharkdb17]$./main.exe
old_line:I LOVE DB
new_line size:84
SELECT name, setting, unit, category, short_desc FROM pg_settings WHERE name like '%
后面两个错误跟第2个是一样的,只是调用了其它系统函数,并且有些系统函数地址不一样
==71981== Invalid read of size 1
==71981== at 0x4EB9ADD: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.17.so)
==71981== by 0x4E8C032: vfprintf (in /usr/lib64/libc-2.17.so)
==71981== by 0x4E924E8: printf (in /usr/lib64/libc-2.17.so)
==71981== by 0x4007DC: main (main.c:22)
==71981== Address 0x520d048 is 0 bytes after a block of size 8 alloc'd
==71981== at 0x4C2B37A: malloc (vg_replace_malloc.c:446)
==71981== by 0x400795: main (main.c:19)
==71981==
==71981== Invalid read of size 1
==71981== at 0x4C38E36: __GI_mempcpy (vg_replace_strmem.c:1701)
==71981== by 0x4EB9A04: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.17.so)
==71981== by 0x4E8C032: vfprintf (in /usr/lib64/libc-2.17.so)
==71981== by 0x4E924E8: printf (in /usr/lib64/libc-2.17.so)
==71981== by 0x4007DC: main (main.c:22)
==71981== Address 0x520d048 is 0 bytes after a block of size 8 alloc'd
==71981== at 0x4C2B37A: malloc (vg_replace_malloc.c:446)
==71981== by 0x400795: main (main.c:19) 最后改成10个字节,就没有这些错误了.
19 tline= malloc(10);
此时此刻,不是标准C11的错! 也不是我的错,第一次理论与实践发生了冲突,通常来讲是理论有问题,这看来是实践有问题,实践也是PGSQL源码编写有问题. 这个锅应该PG17.1背
这事百思不得其解,春节假期过后,思来思去,唯有可能是PG客户端有一套内存回收机制,也就是自己整了一套,
避免NEW个指针的内存泄漏的表.该表登记了内存分配指针指向内存的地址和地址长度.
下个内存分配从前面已分配的地址尾巴开始.类似个链表.
比如这样子 A ->B->C 要是在A和B之间,插入个E 必然是遭遇到我碰到的问题.就是用标准C内存分配函数,
在程序运行过程中,这个顺序,在A之后分配内存给E,后面使用原有的代码再分配内存给B,那么B会在E的内存空间里分配,导致分配报错!
PG本身内存分配行数有个定长分配大小,所以只要字数不超过定长,自然就不会出现任何内存异常.
这样就完美解释了遇到的现象了,虽然没有找出证据出来,主要是其中两个C文件代码是由PERL临时生成的,
编译时候项目文件没有把它俩加入管控中,就是没有夹带调试信息.
解决方案一
就是利用PG套壳内存分配函数去给E分配内存,也就是把E登记在链表里.
方案二
就是在魔改函数里完成 匹配,替换,发送,回收数据,展现数据,循环等待.
这个工作量有点太,不太适合我这样忙里偷闲,国产数据库散户.
合作电话:010-64087828
社区邮箱:greatsql@greatdb.com