GDB的严重bug,无法支持新版本GCC的 inferior call

36 views
Skip to first unread message

asmwarrior

unread,
Oct 5, 2013, 11:37:23 AM10/5/13
to Code::Blocks中国开发组, hell...@freelists.org
Bug和测试代码都在这里,凡是Windows x86下的GCC4.7.x以后编译的C++程序都涉及到:
https://sourceware.org/bugzilla/show_bug.cgi?id=15559
我估计这个bug没这么快能解决。

不知道大虾们有啥好办法?

xunxun

unread,
Oct 5, 2013, 11:53:40 PM10/5/13
to chi...@googlegroups.com, hell...@freelists.org
你试过 set unwindonsignal off 吗?

--
Best Regards,
xunxun

asmwarrior

unread,
Oct 5, 2013, 11:53:00 PM10/5/13
to chi...@googlegroups.com, hell...@freelists.org
On 2013-10-6 11:53, xunxun wrote:
> 于 2013/10/5 星期六 23:37, asmwarrior 写道:
>> Bug和测试代码都在这里,凡是Windows x86下的GCC4.7.x以后编译的C++程序都涉及到:
>> https://sourceware.org/bugzilla/show_bug.cgi?id=15559
>> 我估计这个bug没这么快能解决。
>>
>> 不知道大虾们有啥好办法?
>>
>
> 你试过 set unwindonsignal off 吗?
>
当然,没啥作用。
这个的本质问题是,GDB无法模拟出inferior的thiscall。

asmwarrior

unread,
Oct 6, 2013, 8:46:40 AM10/6/13
to Yao Qi, hell...@freelists.org, Code::Blocks中国开发组
On 2013-10-6 15:57, Yao Qi wrote:
> I have no idea unless we fix it. :)
我对GDB如何产生inferior call的内容一无所知,不知道Yao能否帮忙看看fix这个bug的办法?
呵呵,我估计也就只能帮忙测试了。我现在为了调试程序方便,又退回到使用4.6.x的MinGW GCC了。

asmwarrior

unread,
Oct 8, 2013, 2:32:25 AM10/8/13
to Yao Qi, hell...@freelists.org, Code::Blocks中国开发组
我大概分析了一下GDB产生inferior call的内容。我主要是想看看到底目前的GDB里面,是否有支持两种calling convention的情况,发现其实sh-tdep.c就是这样干的。
我大概写了一个解决此问题的分析思路。

Yao大侠能否帮忙看看这思路大致对不对,主要是需要找一个东西判断一下某个函数是否是 c++的member function,进而采取不同的策略。

我的分析内容放在那个bug后面的comment里面了,见:
https://sourceware.org/bugzilla/show_bug.cgi?id=15559
我估计自己没能力实现了,等待GDB大牛们的实现了,呵呵。

asmwarrior

unread,
Oct 8, 2013, 7:22:27 AM10/8/13
to Yao Qi, hell...@freelists.org, Code::Blocks中国开发组
我自己折腾了一个patch,貌似搞定了这个问题,欢迎大家测试和提供意见。patch见
https://sourceware.org/bugzilla/show_bug.cgi?id=15559


asmwarrior

unread,
Oct 8, 2013, 10:06:49 PM10/8/13
to Yao Qi, hell...@freelists.org, Code::Blocks中国开发组
On 2013-10-9 9:28, Yao Qi wrote:
> On 10/08/2013 02:32 PM, asmwarrior wrote:
>> 我大概分析了一下GDB产生inferior call的内容。我主要是想看看到底目前的GDB里面,是否有支持两种calling convention的情况,发现其实sh-tdep.c就是这样干的。
>
> 能把inferior call 分析清楚,不容易!
> GDB 本身没有 calling convention 的概念,只是在 push_dummy_call 这个函数 中,根据
> calling convention的描述,来摆放register 和 stack。 如果有两种calling convention,就用两种方式摆放。

对的,我的理解也是这样的,在进行dummy call的时候,不外乎就是 register和stack的摆放。

>
>> 我大概写了一个解决此问题的分析思路。
>>
>> Yao大侠能否帮忙看看这思路大致对不对,主要是需要找一个东西判断一下某个函数是否是 c++的member function,进而采取不同的策略。
>
> 思路没有问题,但是考虑不够全面。
>
> GDB 需要能够自动判断程序用的哪种 call convention。_cdecl or _thiscall。 但是不知道有没有办法,可是试试 察看mangled name,也许会有线索。简单的看 函数是否是c++ member function,会有问题的。
是啊,我也希望是否通过查看debug信息能获取那类函数是 _cdecl, 哪类属于 _thiscall。我看了一下编译出来的文件的调试信息:用objdum查看的
(这里是我昨天的log记录)
Test::m_f_c_call()

<2><d6>: Abbrev Number: 8 (DW_TAG_subprogram)
<d7> DW_AT_external : 1
<d8> DW_AT_name : m_f_c_call
<e3> DW_AT_decl_file : 1
<e4> DW_AT_decl_line : 19
<e5> DW_AT_MIPS_linkage_name: _ZNK4Test10m_f_c_callEv
<fd> DW_AT_type : <0x10e>
<101> DW_AT_declaration : 1
<102> DW_AT_object_pointer: <0x106>

Test::m_f_this_call()

<2><95>: Abbrev Number: 7 (DW_TAG_subprogram)
<96> DW_AT_external : 1
<97> DW_AT_name : m_f_this_call
<a5> DW_AT_decl_file : 1
<a6> DW_AT_decl_line : 18
<a7> DW_AT_MIPS_linkage_name: _ZNK4Test13m_f_this_callEv
<c2> DW_AT_type : <0x10e>
<c6> DW_AT_declaration : 1
<c7> DW_AT_object_pointer: <0xcf>
<cb> DW_AT_sibling : <0xd6>
我看不出来mangle name有啥区别,呵呵。



>
>
> 如果实在没有办法判断是哪种call convention,我们才不得已加入命令选项,来 手工设置 call convention。
是的,我看了一下sh-tdep.c里面就是这么干的。

>
> 一些我找到的参考
>
> http://en.wikipedia.org/wiki/X86_calling_conventions
>
> For the GCC compiler, thiscall is almost identical to cdecl: the calling function cleans the stack, and the parameters are passed in right-to-left order. The difference is the addition of the this pointer, which is pushed onto the stack last, as if it were the first parameter in the function prototype.
谢谢,这个帖子我也看了,我还实际在GDB里面用反汇编命令看了函数调用前的stack和寄存器情况。实际上默认情况下,GDB 里面在进行dummy call的时候,就是这样的。只不过GDB默认都把它函数调用作为cdecl在用了。我的修改实际上就是把this参数提取出来,放到ECX里面,当然this参数就不再放入stack了。仅此而已。


>
>>
>> 我的分析内容放在那个bug后面的comment里面了,见:
>> https://sourceware.org/bugzilla/show_bug.cgi?id=15559
>> 我估计自己没能力实现了,等待GDB大牛们的实现了,呵呵。
>
> 那个patch 我看了,缺少注释。
谢谢,我昨天比较晚的时候更新了patch,稍微加了一下注释了,你看是否哪里还缺少了?patch如下:

From 6da10d4303637e69c4139ad1cbe4837f62903470 Mon Sep 17 00:00:00 2001
From: asmwarrior <asmwa...@gmail.com>
Date: Tue, 8 Oct 2013 21:03:46 +0800
Subject: [PATCH 9/9] inferior call change: use thiscall calling convention for
x86 mingw gcc 4.7.0 and above in C++ class method, borrow some code from
sh-tdep.c to handling function typedef and pointers for thiscall calling
convention, a hack to tell i386_push_dummy_call() function that whether it is
a static member function.

---
gdb/eval.c | 7 +++++++
gdb/i386-tdep.c | 33 ++++++++++++++++++++++++++++++++-
2 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/gdb/eval.c b/gdb/eval.c
index e83bfdf..9ae802f 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -49,6 +49,10 @@
/* This is defined in valops.c */
extern int overload_resolution;

+/* this variable is to notify i386_push_dummy_call that an
+ function is static member function, it is a hack */
+extern int i386_windows_static_memfun;
+
/* Prototypes for local functions. */

static struct value *evaluate_subexp_for_sizeof (struct expression *, int *);
@@ -1668,7 +1672,10 @@ evaluate_subexp_standard (struct type *expect_type,
argvec[1] = argvec[0];
nargs--;
argvec++;
+ i386_windows_static_memfun = 1;
}
+ else
+ i386_windows_static_memfun = 0;
}
else if (op == STRUCTOP_MEMBER || op == STRUCTOP_MPTR)
{
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index b159b49..551d630 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -2396,6 +2396,10 @@ i386_push_dummy_code (struct gdbarch *gdbarch, CORE_ADDR sp, CORE_ADDR funaddr,
/* Keep the stack aligned. */
return sp - 16;
}
+/* This is the hack to handle the non-static member function to thiscall
+ calling convention mode, this variable is updated in eval.c of the
+ static member function check */
+int i386_windows_static_memfun = 0;

static CORE_ADDR
i386_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
@@ -2408,6 +2412,27 @@ i386_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
int i;
int write_pass;
int args_space = 0;
+ struct type *func_type = value_type (function);
+ int i386_windows_thiscall = 0;
+
+ if (func_type)
+ {
+ func_type = check_typedef (func_type);
+
+ if (TYPE_CODE (func_type) == TYPE_CODE_PTR)
+ func_type = check_typedef (TYPE_TARGET_TYPE (func_type));
+
+ if( (TYPE_CODE (func_type) == TYPE_CODE_METHOD)
+ && (nargs > 0)
+ && i386_windows_static_memfun == 0 )
+ {
+ /* a.f(5,6);
+ args[0] = this pointer;
+ args[1] = 5;
+ args[2] = 6; */
+ i386_windows_thiscall = 1;
+ }
+ }

/* Determine the total space required for arguments and struct
return address in a first pass (allowing for 16-byte-aligned
@@ -2430,7 +2455,7 @@ i386_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
args_space += 4;
}

- for (i = 0; i < nargs; i++)
+ for (i = i386_windows_thiscall; i < nargs; i++)
{
int len = TYPE_LENGTH (value_enclosing_type (args[i]));

@@ -2482,6 +2507,12 @@ i386_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
/* ...and fake a frame pointer. */
regcache_cooked_write (regcache, I386_EBP_REGNUM, buf);

+ if (i386_windows_thiscall)
+ {
+ /* args[0] refer to the last argument which is the this pointer */
+ regcache_cooked_write (regcache, I386_ECX_REGNUM, value_contents_all(args[0]));
+ }
+
/* MarkK wrote: This "+ 8" is all over the place:
(i386_frame_this_id, i386_sigtramp_frame_this_id,
i386_dummy_id). It's there, since all frame unwinders for
--
1.8.4.msysgit.0

这里有几个问题:
1,注释问题确实有点乱,可以再调整一下,而且我这个patch一打的话,会让GDB只支持MinGW4.7.0以后的版本,把MinGW4.6.x及以前的版本给抛弃掉了,所以最好来检测一下当前装载的BFD二进制文件是啥版本的GCC编译出来的,如果是4.7.0或者以后版本,就进行thiscall的相应调整。

2,GDB里面有一个关于static member function的检测,在eval里面,我在这个地方加了一个全局变量i386_windows_static_memfun:
if (static_memfuncp)
{
argvec[1] = argvec[0];
nargs--;
argvec++;
i386_windows_static_memfun = 1;
}
else
i386_windows_static_memfun = 0;
}
因为我知道(也实际测试过),对于static的member function,实际上是没有this 指针的,GDB里面也进行了 “nargs--”这样的处理。
但是对于dummy_call函数而言,它拥有的信息太少了,我只能判断出来他是不是类成员函数:if( (TYPE_CODE (func_type) == TYPE_CODE_METHOD),另外我就需要用到这个i386_windows_static_memfun变量了,我本来想让这个变量作为函数参数一层一层传递到 dummy call的里面,但是感觉这样对代码改动实在太多了。

谢谢你的回复,希望可以进一步探讨和改进。

asmwarrior

unread,
Oct 9, 2013, 3:01:45 AM10/9/13
to Yao Qi, hell...@freelists.org, Code::Blocks中国开发组
On 2013-10-9 10:06, asmwarrior wrote:
> 这里有几个问题:
> 1,注释问题确实有点乱,可以再调整一下,而且我这个patch一打的话,会让GDB只支持MinGW4.7.0以后的版本,把MinGW4.6.x及以前的版本给抛弃掉了,所以最好来检测一下当前装载的BFD二进制文件是啥版本的GCC编译出来的,如果是4.7.0或者以后版本,就进行thiscall的相应调整。
这个问题已经解决了,我看到了GDB里面有检测目标代码是用什么版本的GCC编译的相关函数。
下面是修改后的总的patch:

* inferior call change: use thiscall calling convention for x86 mingw gcc 4.7.0 and above in C++ class method, borrow some code from sh-tdep.c to handling function typedef and pointers for thiscall calling convention, a hack to tell i386_push_dummy_call() function that whether it is a static member function.
* automatically check the gcc version number, so that this gdb can support multiply gcc versions.



gdb/eval.c | 7 +++++++
gdb/i386-tdep.c | 39 ++++++++++++++++++++++++++++++++++++++-
gdb/infcall.c | 23 +++++++++++++++++++++++
3 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/gdb/eval.c b/gdb/eval.c
index e83bfdf..9ae802f 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -49,6 +49,10 @@
/* This is defined in valops.c */
extern int overload_resolution;

+/* this variable is to notify i386_push_dummy_call that an
+ function is static member function, it is a hack */
+extern int i386_windows_static_memfun;
+
/* Prototypes for local functions. */

static struct value *evaluate_subexp_for_sizeof (struct expression *, int *);
@@ -1668,7 +1672,10 @@ evaluate_subexp_standard (struct type *expect_type,
argvec[1] = argvec[0];
nargs--;
argvec++;
+ i386_windows_static_memfun = 1;
}
+ else
+ i386_windows_static_memfun = 0;
}
else if (op == STRUCTOP_MEMBER || op == STRUCTOP_MPTR)
{
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index b159b49..c7a02bf 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -2396,6 +2396,15 @@ i386_push_dummy_code (struct gdbarch *gdbarch, CORE_ADDR sp, CORE_ADDR funaddr,
/* Keep the stack aligned. */
return sp - 16;
}
+/* This is the hack to handle the non-static member function to thiscall
+ calling convention mode, this variable is updated in eval.c of the
+ static member function check */
+int i386_windows_static_memfun = 0;
+
+/* This is the hack to determine which version of gcc is used to generate
+ the function, if it is greater than 4.6, this use thiscall for member
+ function. */
+int i386_windows_build_gt_gcc46 = 0;

static CORE_ADDR
i386_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
@@ -2408,6 +2417,28 @@ i386_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
int i;
int write_pass;
int args_space = 0;
+ struct type *func_type = value_type (function);
+ int i386_windows_thiscall = 0;
+
+ if (func_type)
+ {
+ func_type = check_typedef (func_type);
+
+ if (TYPE_CODE (func_type) == TYPE_CODE_PTR)
+ func_type = check_typedef (TYPE_TARGET_TYPE (func_type));
+
+ if( (TYPE_CODE (func_type) == TYPE_CODE_METHOD)
+ && i386_windows_build_gt_gcc46 == 1
+ && (nargs > 0)
+ && i386_windows_static_memfun == 0 )
+ {
+ /* a.f(5,6);
+ args[0] = this pointer;
+ args[1] = 5;
+ args[2] = 6; */
+ i386_windows_thiscall = 1;
+ }
+ }

/* Determine the total space required for arguments and struct
return address in a first pass (allowing for 16-byte-aligned
@@ -2430,7 +2461,7 @@ i386_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
args_space += 4;
}

- for (i = 0; i < nargs; i++)
+ for (i = i386_windows_thiscall; i < nargs; i++)
{
int len = TYPE_LENGTH (value_enclosing_type (args[i]));

@@ -2482,6 +2513,12 @@ i386_push_dummy_call (struct gdbarch *gdbarch, struct value *function,
/* ...and fake a frame pointer. */
regcache_cooked_write (regcache, I386_EBP_REGNUM, buf);

+ if (i386_windows_thiscall)
+ {
+ /* args[0] refer to the last argument which is the this pointer */
+ regcache_cooked_write (regcache, I386_ECX_REGNUM, value_contents_all(args[0]));
+ }
+
/* MarkK wrote: This "+ 8" is all over the place:
(i386_frame_this_id, i386_sigtramp_frame_this_id,
i386_dummy_id). It's there, since all frame unwinders for
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 19af044..6d8376c 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -36,6 +36,12 @@
#include "ada-lang.h"
#include "gdbthread.h"
#include "exceptions.h"
+#include "utils.h"
+
+/* this value is defined in i386-tdep.c, and we set this value here
+ if the dummy call function is build from gcc 4.7 and later, we
+ should set this value to 1, other wise, set it to 0 */
+extern int i386_windows_build_gt_gcc46;

/* If we can't find a function's name from its address,
we print this instead. */
@@ -480,6 +486,8 @@ call_function_by_hand (struct value *function, int nargs, struct value **args)
ptid_t call_thread_ptid;
struct gdb_exception e;
char name_buf[RAW_FUNCTION_ADDRESS_SIZE];
+ struct symtab *sym;
+ int gcc_minor;

if (TYPE_CODE (ftype) == TYPE_CODE_PTR)
ftype = check_typedef (TYPE_TARGET_TYPE (ftype));
@@ -745,6 +753,21 @@ call_function_by_hand (struct value *function, int nargs, struct value **args)
}
else
args_cleanup = make_cleanup (null_cleanup, NULL);
+
+ /* before we push the dummy call, we need to use the real_pc to determine
+ what gcc producer is used to generate this function, so it will notify
+ this information to i386-tdep.c */
+ sym = find_pc_symtab(real_pc);
+ if (sym != NULL && sym->producer != NULL)
+ {
+ gcc_minor = producer_is_gcc_ge_4(sym->producer);
+ if (gcc_minor>6)
+ i386_windows_build_gt_gcc46 = 1;
+ else
+ i386_windows_build_gt_gcc46 = 0;
+ }
+ else
+ i386_windows_build_gt_gcc46 = 0;

/* Create the dummy stack frame. Pass in the call dummy address as,
presumably, the ABI code knows where, in the call dummy, the


和前面的patch不同的地方是,在infcall.c添加了gcc版本检测功能,根据不同的GCC产生的inferior进行不同calling convention的切换,只不过还是通过全局变量进行数据交互,看着有点恶心,但是我也不知道有啥好办法。
Reply all
Reply to author
Forward
0 new messages