标签:cpp

以下是与标签 “cpp” 相关联的文章

QT 的两个链接问题

qt-creator 无法显示链接错误

很早就碰到这个问题,一直没有解决。在 qt-creator compile output 窗口中,仅显示 LINK 错误号,没有错误信息。

用命令行编译看看提示信息是什么。

D:\Qt\Qt5.7.0\5.7\msvc2015\bin\qtenv2.bat
"D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
cd /d E:\code\qt\my-project
"D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\BIN\nmake.exe" -f Makefile.Debug

看结果发现,LINK 错误信息中有中文,造成解析错误。下载安装 VS2015 英文语言包,安装并切换成英文,可解决此问题。

LINK 错误,提示找不到 signal 和 metaobject。

排除了各种可能,一直无法解决

  • 没有写 Q_OBJECT
  • 命名空间错误
  • 没有链接 obj 文件
  • 删除中间文件重新编译

最后浏览工程文件时发现,头文件和源文件都是 cpp 文件。修改 pro 文件,把头文件修改正确就行了。

找不到链接库问题

微软抓包工具 NetworkMonitor,自带 SDK,包括头文件和库文件。开发中,用到了这个工具。像往常一样,在代码中包含头文件,在链接选项中添加库文件,但是链接一直出错,提示找不到函数定义。

1>Linking...
 unresolved external symbol _NmCloseHandle@4 referenced in function 
 unresolved external symbol _NmCreateFrameParser@12 referenced in function
 unresolved external symbol _NmAddProperty@12 referenced in function 
 unresolved external symbol _NmConfigConversation@12 referenced in function 
 unresolved external symbol _NmCreateFrameParserConfiguration@16 referenced
 unresolved external symbol _NmLoadNplParser@20 referenced in function 
 unresolved external symbol _NmGetPropertyValueById@32 referenced in 
 unresolved external symbol _NmGetPropertyInfo@12 referenced in function
 unresolved external symbol _NmParseFrame@24 referenced in function _wmain
 unresolved external symbol _NmGetFrame@12 referenced in function _wmain
 unresolved external symbol _NmGetFrameCount@8 referenced in function _wmain

是否没有连接到指定文件 NmApi.lib?

代码里用了 #pragma comment(lib, "NmApi.lib") 来指定链接库,没有指定具体目录,是否目录不对?把 NmApi.lib 换了几个目录,没有解决问题。

修改链接方式,在工程的 Link 选项中指定链接文件,带上指定目录,还是不行。试着修改 lib 文件名称,会提示没有找到,说明链接文件是找到了,但是没有找到对应函数实现。

是否头文件或链接库有问题?

检查头文件,用了 extern C 说明接口调用是 C 风格的。lib 文件里有否有对应函数的定义呢,用 dumpblin 查看,显示结果是有定义的。

E:\code\test\TestNmApi\TestNmApi>dumpbin /exports NmApi.lib
Microsoft (R) COFF/PE Dumper Version 14.00.23026.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file NmApi_x64.lib

File Type: LIBRARY

     Exports

       ordinal    name

                  NmAddDriverCaptureFilter
                  NmAddField
                  NmAddFilter
                  NmAddFrame
                  NmAddOlpBlockToFilter
                  ...
                  NmSetNplProfileAttributeByGuid
                  NmStartCapture
                  NmStopCapture

 Summary

         BD .debug$S
         14 .idata$2
         14 .idata$3
          8 .idata$4
          8 .idata$5
          A .idata$6

是否头文件与库文件函数的 name mangling 不对应?

注意到头文件中,函数前都加了修饰符 WINAPI,定义为 __stdcall,不同的修饰符会使用不同的 namemangling,尝试将 WINAPI 重新定义为 __fastcall __cdecl,都不行。__stdcall 的 name mangling 会在函数前加下划线 ,而 lib 库的导出函数并没有下划线!找到问题了,怎么解决呢?

从 DLL 重新生成 LIB

函数定义已经在那里了,只是 lib 定义不对,那么重新生成 liib 是否可以解决问题?从 stackoverflow 上找来一段脚本,可以从 dll 生成 lib 文件。

dumpbin /exports sqlite3.dll > exports.txt
echo LIBRARY SQLITE3 > sqlite3.def
echo EXPORTS >> sqlite3.def
for /f "skip=19 tokens=4" %A in (exports.txt) do echo %A >> sqlite3.def

lib /def:sqlite3.def /out:sqlite3.lib /machine:x86

很顺利地生成了 lib 文件,dumpbin 查看 lib 导出函数,name mangling 也对了,函数前都加上了下划线。启动链接程序,还是出错。发现 name mangling 还是略有不同,导出函数的名称不带参数个数,尝试把 WINAPI 定义为空,再次启动链接,链接成功。但是运行程序直接就崩溃了。

原来是 64 位程序问题

无计可施了,对比 NetMonitor 安装文件,突然注意到有 64 位和 32 位之分。马上想到安装的 64 位版本,而我编译的是 32 位版本,问题应该就在这个。32 位版本在这台 PC 上无法安装,用 7-zip 解压,得到了对应的 lib 文件。再次链接调试,终于成功了。原来 64 位程序的 name mangling 是不带修饰符的。

把 c++当成脚本语言使用

工作中,经常需要批量解析文本。涉及到功能点主要包括:目录递归、文件读写、字符串解析。功能点不多,但很少有人用 C++做这方面的工作,一般认为脚本语言更适合。因为脚本语法表达更简洁,标准库更全面。C++是门严肃的语言,要写冗长的声明,标准库也不全,需要调用平台 API 完成工作,太繁琐。

这几年,C++标准频繁更新,情况不一样了。C++14 增加了 filesystem 标准库,语法也做了简化,几乎可以用来做为脚本语言。

  • 语法简化了,支持 auto 关键字,不用再写很长的 iterator 声明。
  • 目录递归功能,原来只能使用平台相关的 API,现在可以使用 filesystem 库。PS:在 Windows 平台上,MSVC 从 2015 版本开始支持。cygwin g++ 5.3 未支持。mingw g++才更新到 4.9.2 版本,也不支持。
#include <filesystem>
using namespace experimental::filesystem;

void IteratorDir(const path &p)
{
    for (const auto &iter : directory_iterator(p))
    {
        if (is_directory(iter.status()))
        {
            IteratorDir(iter.path());
        }
        else if (is_regular_file(iter.status()))
        {
            if (iter.path().extension() == ".vcproj")
            {
                // cout << iter.path().filename() << endl;
                ParseVcproj(iter.path());
            }
            else if (iter.path().extension() == ".sln")
            {
                SaveSlnContent(iter.path());
            }
        }
    }
}
  • 文件读写一直还是比较方便的
void SaveSlnContent(const path &p)
{
    ifstream file(p.string().c_str());
    ostringstream ss;
    ss << file.rdbuf();
    m_SlnContent = ss.str();
}
  • 字符串解析功能,各种语言差异不大
void ParseSln()
{
    ofstream out("R:\\a.txt");
    out << "digraph G {" << endl
        << "\trankdir = BT;" << endl
        << endl;

    size_t seg = 0;
    while ((seg = m_SlnContent.find("vcproj\", \"", seg)) != string::npos)
    {
        string curProjId = m_SlnContent.substr(seg + 10, 38);
        string curProjName = m_projectIdMap[curProjId];

        size_t seg2 = m_SlnContent.find("EndProject\n", seg);
        string tmp = m_SlnContent.substr(seg + 48, seg2 - seg - 48);
        seg = seg2;

        size_t pos = 0;
        while ((pos = tmp.find("= {", pos)) != string::npos)
        {
            string dependProjId = tmp.substr(pos + 2, 38);
            string dependProjName = m_projectIdMap[dependProjId];
            if (dependProjName.size() && curProjName.size())
                out << "\t\"" << curProjName << "\" -> \"" << dependProjName
                    << "\"" << endl;
            pos += 38;
        }
    }
    out << "}" << endl;
}
  • 编译问题。Sublime Text 3 里,自带 C++编译支持,但仅支持 g++,不支持 MSVC。添加以下配置后,可以支持 msvc 编译。写完代码后,按 C-S-b 调出编译菜单,选择 msvc Run ,直接编译运行,下方弹出窗口显示运行结果。以后再编译运行时,按 C-b 就可以了,不需要再次选择。
{
    "cmd": ["vcvars32.bat", "&", "cl", "/EHsc", "${file}", "1>&2"],
    "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
    "working_dir": "${file_path}",
    "selector": "source.c, source.cpp, source.c++",
    // By default cl is not in your PATH, so add it to your path (preferably)
    // or uncomment "path" and check that it has correct value
    // "path": "C:\\Windows\\System32;C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin",
    // this also will set path for vcvars32.bat
    "shell": true, // Without this sublime has hard times to parse "&" in out command line

    "variants":
    [
        {
            "name": "Run",
            "cmd": ["vcvars32.bat", "&", "cl", "/EHsc", "${file}", "&", "${file_path}/${file_base_name}.exe"]
        }
    ]
}

总结,filesystem 经常用到的函数如下:

#include <filesystem>
using namespace experimental::filesystem;
path();
path.string();
for (const auto &iter : diretory_iterator(path)) {
    iter.path();
    iter.status();
}

VC 栈结构分析

分析 VC 生成汇编代码的栈帧结构,并比较 release 和 debug 版本的不同。

release 版本

汇编分析

  • 去掉编译优化选项
  • 查看汇编代码

    int fib(int x)
    {
    004017E0  push        ebp  
    004017E1  mov         ebp,esp 
    004017E3  sub         esp,10h 
    004017E6  push        esi  
        int a = 1;
    004017E7  mov         dword ptr [a],1 
        int b = 2;
    004017EE  mov         dword ptr [b],2 
        int c = 3;
    004017F5  mov         dword ptr [c],3 
        int d = 4;
    004017FC  mov         dword ptr [d],4 
    
        if (x == 2)
    00401803  cmp         dword ptr [x],2 
    00401807  jne         fib+33h (401813h) 
        {
          return 1 + 1 + a;
    00401809  mov         eax,dword ptr [a] 
    0040180C  add         eax,2 
    0040180F  jmp         fib+75h (401855h) 
    00401811  jmp         fib+75h (401855h) 
        }
        else if (x == 1)
    00401813  cmp         dword ptr [x],1 
    00401817  jne         fib+43h (401823h) 
        {
          return 1 + 0 + b;
    00401819  mov         eax,dword ptr [b] 
    0040181C  add         eax,1 
    0040181F  jmp         fib+75h (401855h) 
    00401821  jmp         fib+75h (401855h) 
        }
        else if (x == 0)
    00401823  cmp         dword ptr [x],0 
    00401827  jne         fib+50h (401830h) 
        {
          return 0 + c;
    00401829  mov         eax,dword ptr [c] 
    0040182C  jmp         fib+75h (401855h) 
        }
        else
    0040182E  jmp         fib+75h (401855h) 
        {
          return fib(x - 1) + fib(x - 2) + d;
    00401830  mov         eax,dword ptr [x] 
    00401833  sub         eax,1 
    00401836  push        eax  
    00401837  call        fib (4017E0h) 
    0040183C  add         esp,4 
    0040183F  mov         esi,eax 
    00401841  mov         ecx,dword ptr [x] 
    00401844  sub         ecx,2 
    00401847  push        ecx  
    00401848  call        fib (4017E0h) 
    0040184D  add         esp,4 
    00401850  add         esi,dword ptr [d] 
    00401853  add         eax,esi 
        }
    }
    00401855  pop         esi  
    00401856  mov         esp,ebp 
    00401858  pop         ebp  
    00401859  ret
    
  • 分析生成汇编
    • 栈变量的次序是调整过的,防止被 hacker 利用。查看下面的机器指令, E7,EE,F5,FC,显然地址不连续。

      004017E7  mov         dword ptr [a],1 
      004017EE  mov         dword ptr [b],2 
      004017F5  mov         dword ptr [c],3 
      004017FC  mov         dword ptr [d],4
      
    • 被调函数的处理
      • 将 ebp 压栈,保护上层栈基址。

        004017E0  push        ebp
        
      • ebp 指向原来 esp 的位置,指向新的栈基址。

        004017E1  mov         ebp,esp
        
      • esp 留出栈变量的空间,指向新的栈顶。这里留出 4 个 int 的空间 16 个字节:

        004017E3  sub         esp,10h
        
      • 将 esi 压栈,esi 在 fibonaci 函数中被临时使用,这里压栈是为了保护原始值。

        004017E6  push        esi
        
      • 将 esi 退栈

        00401855  pop         esi
        
      • 将 ebp 退栈,恢复上层调用的栈基址

        00401858  pop         ebp
        
      • 将 esp 指向 ebp 的位置,恢复上层调用用栈顶

        00401856  mov         esp,ebp
        
      • 将函数地址退栈,PC 寄存器跳回调用函数

        00401859  ret
        
    • 调用函数的处理
      • 将参数压栈

        00401836  push        eax
        
      • 将函数地址压栈

        00401837  call        fib (4017E0h)
        

栈帧结构

高地址
para1
para2
func addr
ebp
save reg1
save reg2
stack var1
stack var2
低地址

栈帧分析

  • 重点查找 ebp, func addr 两个线索
    • ebp 要连接成串,每个 ebp 指向下一个 ebp;
    • func addr 要在 dll 或 exe 的地址空间内。

debug 版本

汇编分析

  • 生成汇编代码

    int fib(int x)
    {
    004113B0  push        ebp  
    004113B1  mov         ebp,esp 
    004113B3  sub         esp,0F0h 
    004113B9  push        ebx  
    004113BA  push        esi  
    004113BB  push        edi  
    004113BC  lea         edi,[ebp-0F0h] 
    004113C2  mov         ecx,3Ch 
    004113C7  mov         eax,0CCCCCCCCh 
    004113CC  rep stos    dword ptr es:[edi] 
        int a = 1;
    004113CE  mov         dword ptr [a],1 
        int b = 2;
    004113D5  mov         dword ptr [b],2 
        int c = 3;
    004113DC  mov         dword ptr [c],3 
        int d = 4;
    004113E3  mov         dword ptr [d],4 
    
        if (x == 2)
    004113EA  cmp         dword ptr [x],2 
    004113EE  jne         fib+4Ah (4113FAh) 
        {
          return 1 + 1 + a;
    004113F0  mov         eax,dword ptr [a] 
    004113F3  add         eax,2 
    004113F6  jmp         fib+8Ch (41143Ch) 
    004113F8  jmp         fib+8Ch (41143Ch) 
        }
        else if (x == 1)
    004113FA  cmp         dword ptr [x],1 
    004113FE  jne         fib+5Ah (41140Ah) 
        {
          return 1 + 0 + b;
    00411400  mov         eax,dword ptr [b] 
    00411403  add         eax,1 
    00411406  jmp         fib+8Ch (41143Ch) 
    00411408  jmp         fib+8Ch (41143Ch) 
        }
        else if (x == 0)
    0041140A  cmp         dword ptr [x],0 
    0041140E  jne         fib+67h (411417h) 
        {
          return 0 + c;
    00411410  mov         eax,dword ptr [c] 
    00411413  jmp         fib+8Ch (41143Ch) 
        }
        else
    00411415  jmp         fib+8Ch (41143Ch) 
        {
          return fib(x - 1) + fib(x - 2) + d;
    00411417  mov         eax,dword ptr [x] 
    0041141A  sub         eax,1 
    0041141D  push        eax  
    0041141E  call        fib (411154h) 
    00411423  add         esp,4 
    00411426  mov         esi,eax 
    00411428  mov         ecx,dword ptr [x] 
    0041142B  sub         ecx,2 
    0041142E  push        ecx  
    0041142F  call        fib (411154h) 
    00411434  add         esp,4 
    00411437  add         esi,dword ptr [d] 
    0041143A  add         eax,esi 
        }
    }
    0041143C  pop         edi  
    0041143D  pop         esi  
    0041143E  pop         ebx  
    0041143F  add         esp,0F0h 
    00411445  cmp         ebp,esp 
    00411447  call        @ILT+320(__RTC_CheckEsp) (411145h) 
    0041144C  mov         esp,ebp 
    0041144E  pop         ebp  
    0041144F  ret
    
  • 栈变量空间至少保留 0xF0 = 240 字节的空间

    004113B3  sub         esp,0F0h
    
  • 栈变量空间被初始化成 0xCC

    004113BC  lea         edi,[ebp-0F0h] 
    004113C2  mov         ecx,3Ch 
    004113C7  mov         eax,0CCCCCCCCh 
    004113CC  rep stos    dword ptr es:[edi]
    
  • 退栈时会作 ebp 检查,防止栈被破坏

    0041143F  add         esp,0F0h 
    00411445  cmp         ebp,esp 
    00411447  call        @ILT+320(__RTC_CheckEsp) (411145h)
    

栈帧结构

  • 栈变量的分配留有很大裕量,为每个 int 分配了 4*4=16 个字节空间。