是什么导致此指针的值从简单的函数调用更改?

时间:2016-11-22 00:28:30

标签: c++ windows pointers visual-c++ dll

在这种情况下,几张照片值几千字,所以我将首先描述我观察的内容。这是我的第一个展览,我将其称为"堆叠框架1":

stack frame 1

这里我传入一个类型为FLValue的参数(它是指向定义所调用函数的DLL内部的C ++类型的指针的typedef)到一个名为{{1}的函数}。此时参数的值为0x0486d10e。

现在请注意我的第二个展览,我将打电话给#34;堆叠第2帧":

stack frame 2

此堆栈帧位于堆栈帧1的正上方,是输入函数FLValue_AsString的结果。据我所知,调试器立即执行并且没有采用任何内联的路由(这是在所有禁用的优化运行的情况下运行的)。但是,奇怪的是,请注意FLValue_AsString(上面的v)的值现在是0xedcf6fc2。这显然会导致内存访问无效导致崩溃。刚刚发生了什么事?

其他一些信息:

  • 编译器设置为在解决方案的所有项目中将函数编译为FLValue
  • 有问题的函数在__cdecl
  • 中定义
  • LiteCore.dll(暴露extern "C"的DLL)不直接编译文件,而是一种将三个静态库和另外两个dll链接在一起的集合体,并定义了暴露符号的定义文件和所以函数实际上是在其中一个静态库里面定义的。
  • 这不是我观察到这种行为的唯一地方,尽管大多数调用正常运行
  • 如果我手动将调试器中的内存地址更改为堆栈帧1中的内存地址,那么我可以在没有无效内存访问的情况下继续
  • 在OS X上使用Apple Clang或在Ubuntu上使用GCC编译时,没有发现这些问题

编辑屏幕截图中的文字形式的一些信息:

Trace @ stack frame 1(从main()开始):

FLValue_AsString

传递给C4Tests.exe!PerfTest::insertDocs(const _FLArray * docs) Line 141 C++ C4Tests.exe!`anonymous namespace'::____C_A_T_C_H____T_E_S_T____491::test() Line 501 C++ C4Tests.exe!Catch::NWayMethodTestCase<`anonymous namespace'::____C_A_T_C_H____T_E_S_T____491>::invoke() Line 29 C++ C4Tests.exe!Catch::TestCase::invoke() Line 7519 C++ C4Tests.exe!Catch::RunContext::invokeActiveTestCase() Line 6159 C++ C4Tests.exe!Catch::RunContext::runCurrentTest(std::basic_string<char,std::char_traits<char>,std::allocator<char> > & redirectedCout, std::basic_string<char,std::char_traits<char>,std::allocator<char> > & redirectedCerr) Line 6131 C++ C4Tests.exe!Catch::RunContext::runTest(const Catch::TestCase & testCase) Line 5951 C++ C4Tests.exe!Catch::runTests(const Catch::Ptr<Catch::Config> & config) Line 6297 C++ C4Tests.exe!Catch::Session::run() Line 6405 C++ C4Tests.exe!Catch::Session::run(int argc, const char * const * const argv) Line 6384 C++ C4Tests.exe!main(int argc, char * * argv) Line 10333 C++ 的参数值:

  

foo 0x0486d10e {_byte = 0x0486d10e&#34; FRem ...} const fleece :: Value *

Trace @ stack frame 2:

FLValue_AsString()

LiteCore.dll!FLValue_AsString(const fleece::Value * v) Line 56 C++ C4Tests.exe!PerfTest::insertDocs(const _FLArray * docs) Line 141 C++ C4Tests.exe!`anonymous namespace'::____C_A_T_C_H____T_E_S_T____491::test() Line 501 C++ C4Tests.exe!Catch::NWayMethodTestCase<`anonymous namespace'::____C_A_T_C_H____T_E_S_T____491>::invoke() Line 29 C++ C4Tests.exe!Catch::TestCase::invoke() Line 7519 C++ C4Tests.exe!Catch::RunContext::invokeActiveTestCase() Line 6159 C++ C4Tests.exe!Catch::RunContext::runCurrentTest(std::basic_string<char,std::char_traits<char>,std::allocator<char> > & redirectedCout, std::basic_string<char,std::char_traits<char>,std::allocator<char> > & redirectedCerr) Line 6131 C++ C4Tests.exe!Catch::RunContext::runTest(const Catch::TestCase & testCase) Line 5951 C++ C4Tests.exe!Catch::runTests(const Catch::Ptr<Catch::Config> & config) Line 6297 C++ C4Tests.exe!Catch::Session::run() Line 6405 C++ C4Tests.exe!Catch::Session::run(int argc, const char * const * const argv) Line 6384 C++ C4Tests.exe!main(int argc, char * * argv) Line 10333 C++ C4Tests.exe!invoke_main() Line 64 C++ 收到的参数值:

  

v 0xedcf6fc2 {_byte = 0xedcf6fc2} const fleece :: Value *

编辑2 :另一个奇怪的信息是,在64位版本中,调试器显示内存为0xcccccccccccccccc,实际上内存很好并且程序继续没有问题。

编辑3 :编译器选项:

C4Tests.exe:

  

/ GS / TP / analyze- / W3 / Zc:wchar_t / Zi / Gm- / Od / Ob0 /Fd"C4Tests.dir\Debug\vc140.pdb" / Zc:inline / fp:exact / D&#34; WIN32&#34; / D&#34; _WINDOWS&#34; / D&#34; _DEBUG&#34; / D&#34; DEBUG&#34; / D&#34; C4DB_THREADSAFE&#34; / D&#34; SQLITE_OMIT_LOAD_EXTENSION&#34; / D&#34; C4_TESTS&#34; / D&#34; CMAKE_INTDIR = \&#34; Debug \&#34;&#34; / D&#34; _MBCS&#34; / errorReport:prompt / WX- / Zc:forScope / RTC1 / GR / Gd / Oy- / MDd / Fa&#34; Debug /&#34; / EHsc / nologo /Fo&#34; C4Tests.dir\Debug;&#34; /Fp"C4Tests.dir\Debug\C4Tests.pch"

LiteCore.dll:

  

/ GS / TP / analyze- / W3 / Zc:wchar_t / Zi / Gm- / Od / Ob0 /Fd"LiteCore.dir\Debug\vc140.pdb" / Zc:inline / fp:exact / D&#34; WIN32&#34; / D&#34; _WINDOWS&#34; / D&#34; _DEBUG&#34; / D&#34; DEBUG&#34; / D&#34; C4DB_THREADSAFE&#34; / D&#34; SQLITE_OMIT_LOAD_EXTENSION&#34; / D&#34; CMAKE_INTDIR = \&#34; Debug \&#34;&#34; / D&#34; LiteCore_EXPORTS&#34; / D&#34; _WINDLL&#34; / D&#34; _MBCS&#34; / errorReport:prompt / WX- / Zc:forScope / RTC1 / GR / Gd / Oy- / MDd / Fa&#34; Debug /&#34; / EHsc / nologo /Fo&#34; LiteCore.dir\Debug;&#34; /Fp"LiteCore.dir\Debug\LiteCore.pch"

FleeceStatic:

  

/ GS / TP / analyze- / W3 / Zc:wchar_t / Zi / Gm- / Od / Ob0 /Fd"FleeceStatic.dir\Debug\FleeceStatic.pdb" / Zc:inline / fp:exact / D&#34; WIN32&#34; / D&#34; _WINDOWS&#34; / D&#34; _DEBUG&#34; / D&#34; DEBUG&#34; / D&#34; CMAKE_INTDIR = \&#34; Debug \&#34;&#34; / D&#34; _MBCS&#34; / errorReport:prompt / WX- / Zc:forScope / RTC1 / GR / Gd / Oy- / MDd / Fa&#34; Debug /&#34; / EHsc / nologo /Fo&#34; .FleeceStatic.dir\Debug;&#34; /Fp"FleeceStatic.dir\Debug\FleeceStatic.pch"

链接选项:

C4Tests.exe:

  

/ OUT:&#34; d:\开发\ couchbase-精简版芯\ build_cmake \ C \测试\调试\ C4Tests.exe&#34; / MANIFEST / NXCOMPAT /PDB:" D:/Development/couchbase-lite-core/build_cmake/C/tests/Debug/C4Tests.pdb" / DYNAMICBASE&#34; kernel32.lib&#34; &#34; USER32.LIB&#34; &#34; GDI32.LIB&#34; &#34; winspool.lib&#34; &#34; SHELL32.LIB&#34; &#34; ole32.lib&#34; &#34; oleaut32.lib&#34; &#34; UUID.LIB&#34; &#34; comdlg32.lib&#34; &#34; advapi32.lib&#34; &#34; .... \调试\ LiteCore.lib&#34; &#34; .... \厂商\ SQLiteCpp \ sqlite3的\调试\ sqlite3.lib&#34; &#34; .... \厂商\ forestdb \调试\ forestdb.lib&#34; &#34; ...... \厂商\ OpenSSL的\库\ Windows \ 86 \ libeay32.lib&#34; /IMPLIB:"D:/Development/couchbase-lite-core/build_cmake/C/tests/Debug/C4Tests.lib" / DEBUG / MACHINE:X86 / SAFESEH / INCREMENTAL /PGD:" D:\Development\couchbase-lite-core\build_cmake\C\tests\Debug\C4Tests.pgd" / SUBSYSTEM:CONSOLE / MANIFESTUAC:&#34; level =&#39; asInvoker&#39; uiAccess =&#39;假&#39;&#34; /ManifestFile:"C4Tests.dir\Debug\C4Tests.exe.intermediate.manifest" / ERRORREPORT:PROMPT / NOLOGO / TLBID:1

LiteCore.dll:

  

/ OUT:&#34; d:\开发\ couchbase-精简版芯\ build_cmake \调试\ LiteCore.dll&#34; / MANIFEST / NXCOMPAT /PDB:" D:/Development/couchbase-lite-core/build_cmake/Debug/LiteCore.pdb" / DYNAMICBASE&#34; kernel32.lib&#34; &#34; USER32.LIB&#34; &#34; GDI32.LIB&#34; &#34; winspool.lib&#34; &#34; SHELL32.LIB&#34; &#34; ole32.lib&#34; &#34; oleaut32.lib&#34; &#34; UUID.LIB&#34; &#34; comdlg32.lib&#34; &#34; advapi32.lib&#34; &#34;调试\ LiteCoreStatic.lib&#34; &#34;供应商\羊毛\调试\ FleeceStatic.lib&#34; &#34;供应商\ sqlite3的-unicodesn \调试\ SQLite3_UnicodeSN.lib&#34; &#34;供应商\ SQLiteCpp \ sqlite3的\调试\ sqlite3.lib&#34; &#34;供应商\ forestdb \调试\ forestdb.lib&#34; &#34; WS2_32.LIB&#34; &#34; .. \厂商\ OpenSSL的\库\ Windows \ 86 \ libeay32.lib&#34; /IMPLIB:"D:/Development/couchbase-lite-core/build_cmake/Debug/LiteCore.lib" / DEBUG / DLL / MACHINE:X86 / SAFESEH / INCREMENTAL /PGD:"D:\Development\couchbase-lite-core\build_cmake\Debug\LiteCore.pgd" / SUBSYSTEM:CONSOLE / MANIFESTUAC:&#34; level =&#39; asInvoker&#39; uiAccess =&#39;假&#39;&#34; /ManifestFile:"LiteCore.dir\Debug\LiteCore.dll.intermediate.manifest" / ERRORREPORT:PROMPT / NOLOGO / TLBID:1

FleeceStatic:

  

/ OUT:&#34; d:\开发\ couchbase-精简版芯\ build_cmake \厂商\羊毛\调试\ FleeceStatic.lib&#34; / NOLOGO

编辑4 :让我们进一步深入到发射的组件中。在致电FLValue_AsString()

之前开始
FLValue_ToString()

所以我猜这看起来好像搞砸了约会呢?这似乎是滥用ecx而不是eax?

回复关于FLSLICE的问题: 在外部,它的定义如下:

mov eax,dword ptr [ebp-1B4h] // move foo's address into eax push eax call _FLValue_AsString (0144AC9Eh) jmp dword ptr [__imp__FLValue_AsString (014BD208h)] jmp FLValue_AsString (0187DE00h) push ebp mov ebp,esp sub esp,18h mov eax,0CCCCCCCCh mov dword ptr [ebp-18h],eax mov dword ptr [ebp-14h],eax mov dword ptr [ebp-10h],eax mov dword ptr [ebp-0Ch],eax // this should be v (the passed value ?) mov dword ptr [ebp-8],eax mov dword ptr [ebp-4],eax cmp dword ptr [v],0 // v == ebp+0Ch je FLValue_AsString+34h (0187DE34h) // no jump here lea eax,[ebp-0Ch] // eax is overwritten with a pointer to 0xcccccccc push eax mov ecx,dword ptr [v] // at this point v is garbage call fleece::Value::asString (016E4DE5h) jmp fleece::Value::asString (01882DB0h) ... mov dword ptr [this],eax mov dword ptr [this],ecx // Why the double move here? Both are invalid anyway...

在静态库内部,它是一个具有相同成员变量的结构的typedef,并添加了一些用于内部操作的方法,如here所示。

1 个答案:

答案 0 :(得分:2)

我没有关于Visual C ++调用约定的专家,但我强烈怀疑两个模块之间FLSlice声明的差异导致调用者期望返回值通过寄存器传递并且被调用者期望它在内存中传递。这导致对哪个参数代表有问题的指针存在分歧。

来自维基百科上的X86 calling conventions

  

cdecl的解释有一些变化,特别是如何返回值。 [...]某些编译器在寄存器对EAX:EDX中返回长度为2个寄存器或更少的简单数据结构,以及需要异常处理程序进行特殊处理的较大结构和类对象(例如,定义的构造函数,析构函数或赋值)在内存中返回。要在内存中传递&#34;&#34;,调用者分配内存并将指针作为隐藏的第一个参数传递给它;被调用者填充内存并返回指针,返回时弹出隐藏的指针。

特别注意构造函数的存在会影响调用约定。