是啥导致这个指针的值从一个简单的函数调用发生变化?

Posted

技术标签:

【中文标题】是啥导致这个指针的值从一个简单的函数调用发生变化?【英文标题】:What is causing the value of this pointer to change from a simple function call?是什么导致这个指针的值从一个简单的函数调用发生变化? 【发布时间】:2016-11-22 00:28:30 【问题描述】:

在这种情况下,几张照片值几千字,所以我将从描述我所观察到的内容开始。这是我的第一个展览,我将其称为“堆栈框架 1”:

在这里,我将FLValue 类型的参数(它是指向C++ 类型的指针的typedef,该指针位于定义被调用函数的DLL 的内部)到一个名为FLValue_AsString 的函数。此时参数的值为0x0486d10e。

现在请注意我的第二个展览,我将其称为“堆栈框架 2”:

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

其他一些信息:

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

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

跟踪@堆栈帧 1(从 main() 开始):

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++

传入FLValue_AsString()的参数值:

foo 0x0486d10e _byte=0x0486d10e "FRem... const 羊毛::Value *

跟踪@堆栈帧 2:

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++

FLValue_AsString()接收到的参数值:

v 0xedcf6fc2 _byte=0xedcf6fc2 常量羊毛::值 *

编辑 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:precise /D "WIN32 " /D "_WINDOWS" /D "_D​​EBUG" /D "DEBUG" /D "C4DB_THREADSAFE" /D "SQLITE_OMIT_LOAD_EXTENSION" /D "C4_TESTS" /D "CMAKE_INTDIR=\"Debug\"" /D "_MBCS" /errorReport:提示 /WX- /Zc:forScope /RTC1 /GR /Gd /Oy- /MDd /Fa"Debug/" /EHsc /nologo /Fo"C4Tests.dir\Debug\" /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:precise /D"WIN32 " /D "_WINDOWS" /D "_D​​EBUG" /D "DEBUG" /D "C4DB_THREADSAFE" /D "SQLITE_OMIT_LOAD_EXTENSION" /D "CMAKE_INTDIR=\"Debug\"" /D "LiteCore_EXPORTS" /D "_WINDLL" /D " _MBCS" /errorReport:prompt /WX- /Zc:forScope /RTC1 /GR /Gd /Oy- /MDd /Fa"Debug/" /EHsc /nologo /Fo"LiteCore.dir\Debug\" /Fp"LiteCore.dir \调试\LiteCore.pch"

羊毛静电:

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

链接选项:

C4Tests.exe:

/OUT:"D:\Development\couchbase-lite-core\build_cmake\C\tests\Debug\C4Tests.exe" /MANIFEST /NXCOMPAT /PDB:"D:/Development/couchbase-lite-core/build_cmake /C/tests/Debug/C4Tests.pdb”/DYNAMICBASE“kernel32.lib”“user32.lib”“gdi32.lib”“winspool.lib”“shell32.lib”“ole32.lib”“oleaut32.lib”“uuid .lib" "comdlg32.lib" "advapi32.lib" "....\Debug\LiteCore.lib" "....\vendor\SQLiteCpp\sqlite3\Debug\sqlite3.lib" "..\vendor \forestdb\Debug\forestdb.lib" "......\vendor\openssl\libs\windows\x86\libeay32.lib" /IMPLIB:"D:/Development/couchbase-lite-core/build_cmake/C/测试/调试/C4Tests.lib" /DEBUG /MACHINE:X86 /SAFESEH /INCREMENTAL /PGD:"D:\Development\couchbase-lite-core\build_cmake\C\tests\Debug\C4Tests.pgd" /SUBSYSTEM:CONSOLE / MANIFESTUAC:"level='asInvoker' uiAccess='false'" /ManifestFile:"C4Tests.dir\Debug\C4Tests.exe.intermediate.manifest" /ERRORREPORT:PROMPT /NOLOGO /TLBID:1

LiteCore.dll:

/OUT:"D:\Development\couchbase-lite-core\build_cmake\Debug\LiteCore.dll" /MANIFEST /NXCOMPAT /PDB:"D:/Development/couchbase-lite-core/build_cmake/Debug/LiteCore .pdb” /DYNAMICBASE “kernel32.lib” “user32.lib” “gdi32.lib” “winspool.lib” “shell32.lib” “ole32.lib” “oleaut32.lib” “uuid.lib” “comdlg32.lib” “advapi32.lib” “Debug\LiteCoreStatic.lib” “供应商\fleece\Debug\FleeceStatic.lib” “供应商\sqlite3-unicodesn\Debug\SQLite3_UnicodeSN.lib” “供应商\SQLiteCpp\sqlite3\Debug\sqlite3.lib” “ vendor\forestdb\Debug\forestdb.lib" "Ws2_32.lib" "..\vendor\openssl\libs\windows\x86\libeay32.lib" /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:"level ='asInvoker' uiAccess='false'" /ManifestFile:"LiteCore.dir\Debug\LiteCore.dll.intermediate.manifest" /ERRORREPORT:PROMPT /NOLOGO /TLBID:1

羊毛静电:

/OUT:"D:\Development\couchbase-lite-core\build_cmake\vendor\fleece\Debug\FleeceStatic.lib" /NOLOGO

编辑 4:让我们进一步深入研究发出的程序集。在通话前开始FLValue_ToString()

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...

所以我想这看起来毕竟是混乱的调用约定?这似乎是滥用 ecx 而不是 eax?

回复关于 FLSLICE 的问题: 在外部它是这样定义的:

typedef struct const void *buf; size_t size; FLSlice;

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

【问题讨论】:

Itty bitty 截图没有帮助。请创建一个Minimal, Complete, and Verifiable example。我希望您代表的人现在知道这一点.... 您是在移动设备上查看此内容吗?截图远非一点点(如果你点击它们,它们是 1080p)。不过我会处理第二部分。 对不起,背后有一个邪恶的工作代理。无法点击它们。 所以无法搜索/索引屏幕截图。请使用文字。 您可以复制/粘贴源代码和堆栈跟踪。这比将图像剪辑到相关部分要快。您使用什么编译器选项? 【参考方案1】:

我不是 Visual C++ 调用约定方面的专家,但我强烈怀疑两个模块之间 FLSlice 声明的差异导致调用者期望返回值通过寄存器传递,而被调用者期望它要在内存中传递。这导致在哪个参数代表所讨论的指针方面存在分歧。

来自***上的X86 calling conventions:

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

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

【讨论】:

先生,您确实是专家,因为它修复了它并解释了为什么它可以在 x64 上运行!我安装了一个快速尖峰来在两个转换之间来回转换而不是 typedef,我看到程序集更改为 ebp + 8!

以上是关于是啥导致这个指针的值从一个简单的函数调用发生变化?的主要内容,如果未能解决你的问题,请参考以下文章

传入参数 指针 引用和 什么都不加的区别

C#中的委托是啥?事件是否一种委托?

STM32F103因为栈空间过小导致的奇怪问题!

Knockout.js:当可观察数组中的值发生变化时触发计算的最佳方法是啥

当我将值从服务传递到我的角度组件时,结果错误

为啥这个函数调用的执行时间会发生变化?