Seeker.Log

一个笨拙的探索者的思考

0%

linux项目移植到Windows下CMake改造

背景

因项目工程需要,将linux下的AProject项目迁移到Windows下,对代码进行适配,编译选项进行修改。
通过CMake配置Windows(MSVC编译器)选项,进行跨平台移植。

Windows下工具链

CMake+Visual Studio 2022

CMake调整关键点

关键片段

1
2
3
4
5
6
7
8
9
10
11
if(MSVC)
option(BUILD_SHARED_LIBS "Build shared libraries (.dll)." OFF)
add_compile_definitions(_USE_MATH_DEFINES)
add_compile_definitions(NOMINMAX)
string(APPEND CMAKE_CXX_FLAGS " /diagnostics:classic /utf-8 /MP /bigobj /EHsc /W3")
string(APPEND CMAKE_C_FLAGS " /diagnostics:classic /utf-8 /MP /bigobj /W3")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:10000000")
if(MSVC_VERSION GREATER_EQUAL 1914)
add_compile_options(/Zc:__cplusplus)
endif()
endif()

这段 CMake 配置代码是专门为 Windows (MSVC 编译器) 准备的“大补丁包”。

它的主要目的是:让 Visual Studio 的行为更像 Linux 下的 GCC/Clang,同时解决 Windows 特有的一些“坑”。

下面逐条详细解释:

1. 库的构建方式

1
option(BUILD_SHARED_LIBS "Build shared libraries (.dll)." OFF)
  • 含义:定义一个开关,默认设置为 OFF
  • 作用:告诉 CMake 默认编译 静态库 (.lib) 而不是 **动态库 (.dll)**。
  • 场景:如果你不想在运行 exe 时拖着一堆 .dll 文件到处跑,或者不想处理复杂的 dll 导出符号(__declspec(dllexport)),用静态库是最省事的。

2. 预处理器定义 (Preprocessor Definitions)

1
add_compile_definitions(_USE_MATH_DEFINES)
  • 含义:开启数学常量定义。
  • 为什么需要:标准 C++ 其实不强制要求提供 M_PI (圆周率) 这种宏。Linux 的 math.h 默认有,但 Windows 为了严格符合标准默认把它藏起来了。
  • 效果:加上它,你才能在代码里愉快地使用 M_PIM_PI_2 等宏,否则会报错“未声明的标识符”。
1
add_compile_definitions(NOMINMAX)
  • 含义这是一个救命的宏。 禁用 Windows 头文件中的 minmax 宏。
  • 为什么需要:Windows 的 <windows.h> 历史遗留问题,它定义了全局宏 min(a,b)max(a,b)。这会严重干扰 C++ 标准库的 std::minstd::max
  • 效果:如果不加这个,当你写 std::max(1, 2) 时,预处理器会把它替换成错误的乱码从而编译失败。

3. 编译器标志 (Compile Flags)

这些标志被追加到了 CMAKE_CXX_FLAGS (C++) 和 CMAKE_C_FLAGS (C) 中。

  • /diagnostics:classic

    • 含义:设置报错信息的格式为经典模式。
    • 作用:VS2022 新版有时候报错信息太花哨,而且有时候VS界面读不懂编译器输出把真正的编译错误给吞掉(如MSB8084“CL.exe”的结构化输出无效: 无法分析 JsonRpc 通知:“Cannot transcode invalid UTF-8 JSON text to UTF-16 string.”这种报错。),用这个可以让输出格式变回 文件名(行号): error ...,更清晰。
  • /utf-8

    • 含义强制源文件和执行字符集都为 UTF-8。
    • 作用极其重要。Linux 代码通常是 UTF-8 的,而 Windows 中文环境默认是 GBK。
    • 场景:如果不加这个,你的代码里如果有中文字符串(比如 printf("你好")),打印出来就是乱码;或者代码里有中文注释,可能会导致编译报错。
  • /MP

    • 含义多进程编译 (Multi-Processor)。
    • 作用加速神器。它允许 VS 同时启动多个编译器进程来编译不同的源文件,利用多核 CPU。不加这个,编译速度会慢很多。
  • /bigobj

    • 含义:允许生成更大的对象文件(增加节的数量限制)。
    • 作用:**防止 fatal error C1128**。
    • 场景:如果你的代码里大量使用了模板(Template)、或者某个 .cpp 文件特别巨大(几万行),生成的 .obj 文件会超过默认限制。加上它就能解决。
  • /EHsc (仅 C++)

    • 含义:启用标准 C++ 异常处理模型。
    • 作用:告诉编译器捕捉 C++ 的 try-catch 异常,并假设 extern "C" 的函数不会抛出异常。这是 VS 编译 C++ 代码的标准姿势。
  • /W3

    • 含义:警告等级 3 (Warning Level 3)。
    • 作用:开启大部分常用的警告。等级从 /W0 (不警告) 到 /W4 (极度严格)。/W3 是一个比较平衡的生产环境选择。

4. 链接器标志 (Linker Flags)

1
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:10000000")
  • 含义:设置主线程栈大小为 ~10MB。
  • 作用:防止栈溢出(Stack Overflow)。
    详细讨论可以看另一篇博客:
    增大函数栈内存空间

5. 语言标准修正

1
2
3
if(MSVC_VERSION GREATER_EQUAL 1914)
add_compile_options(/Zc:__cplusplus)
endif()
  • 含义:如果 MSVC 版本大于等于 1914 (VS 2017 15.7),则启用 /Zc:__cplusplus
  • 为什么需要:这是一个历史坑。默认情况下,即使你开启了 C++17 或 C++20,MSVC 里的 __cplusplus 宏的值依然是 199711L(老标准)。这会导致很多跨平台库(如 Boost, Qt)误以为编译器不支持新特性。
  • 效果:加上这个后,__cplusplus 宏就会正确报告版本号(如 C++17 会报告 201703L),让代码能正确识别当前的语言标准。

总结

这份配置可以解决很多项目代码无关平台特性强相关的报错,它把 Visual Studio 调教得更符合现代 C++ 标准和跨平台开发习惯
如果不加这些配置,直接把 Linux 代码拿过来编译,通常会遇到中文乱码、数学库报错、min/max 冲突等一堆琐碎问题。。。

libcryptopp源码编译

详细步骤

1.下载源码,解压
2.修改GNUmakefile
因为是64位编译32位的库,还增加了-m32选项

1
2
3
4

CXXFLAGS += -pipe -fPIC
CXXFLAGS += -m32

如下图:
cryptopp_complie

3.然后make

1
2
3

make libcryptopp.a libcryptopp.so cryptest

cryptopp_complie_2

4.然后安装make install

1
2
3
4

可以指定安装路径,如不想安装到默认路径
make install PREFIX=/usr/local/selfpath

cryptopp_complie_3

参考链接

https://www.lwlwq.com/post-cryptopp.html

常用工具

记录下常用的工具,持续更新

visual studio code

生产力工具,不管是写markdown文档,渲染mermaid图,写代码等等,都很好用!

配置C++,git,中文相关插件

git插件,c++插件都是装机必备,非常方便

vs_git.jpg

配置展示多行标签页

如果打开的标签页太多了,默认都是排一行,新的展示旧的被挤到后面去看不见了,这时选择多行展示标签页,会很方便

1.按住CTRL+SHIFT+P弹出搜索框,选中“首选项:打开工作区设置”

vs_set.jpg

2.打开设置页面后,搜索“wrap”,选中“Wrap Tabs”就可以展示多行标签了

vs_wrap.jpg

3.最后效果如下,框选出来的都是打开的标签页,可以展示多行了:

vs_show.jpg

PEView, ProcessExploer,vbox, valgrind,wireshark

这些找具体例子结合的时候再详说

截图神器Snipaste

非常好用的截图神器,直接从官网下载了解压即可使用
常用功能
1.当Snipaste启动时,按下F1即可对屏幕进行截图
可以在截图时用“W、A、S、D”和键盘方向键分别对鼠标和截屏区域进行像素级移动

2.标注功能也很强大

3.按下F3可以贴图

画图-一个被低估的神器

电脑自带的软件-画图 是一个被低估的神器
尤其是在做UI界面相关非常精细的工作的时候,如果需要1px的精度,那么就可以借助画图软件,
勾选“标尺”或“网格线”进行图片的放大缩小对比,可以非常好的看出来设计图和实现的图的效果对比
做很精细的UI的时候是一个非常好用的工具
paint.jpg

redis交叉编译arm版本

背景

遇到以下报错时,记得更新redis版本,用最新的redis代码编译
redis_error

编译环境

在预先已经配置好交叉编译环境的虚拟机里,编译arm版本的redis

编译步骤

1.修改redis/depsMakefile,在jemalloc编译的时候,在./configure增加 --host=arm-linux选项

redis_makefile

redis_configure

2.在redis下直接执行makefile,如果之前编译过,先执行make distclean在彻底清除之前编译的残留,出现以下内容,就是编译完成了

redis_make

3.查看编译出来的执行文件,比如redis-cli ,可以发现编译出来的redis-cli目标机器是arm的,交叉编译结束

redis_check

1. 背景:开机自启动程序无法弹出界面?

在开发 Windows 后台服务(Service)或开机自启程序时,经常遇到一个棘手的问题:

场景描述:
有一个 GUI 程序 getCode.exe 需要开机自启动并与用户交互(例如弹出窗口让用户输入)。

  • 尝试 1:最初直接使用 nssmgetCode.exe 注册为 Windows 服务。
    • 结果:程序启动了,但在后台默默运行,用户界面完全看不到,对键盘鼠标无反应。
  • 尝试 2:开机后手动双击运行 getCode.exe
    • 结果:程序正常显示,交互正常。

原因分析:
通过上网翻找资料,查看任务管理器后发现:

  • 通过 nssm 启动的进程,其 会话 ID (Session ID)0
  • 用户手动启动的进程,其 会话 ID1(或更高)。
  • 当前登录用户正在操作的桌面属于 Session 1,而 Session 0 的程序无法直接在 Session 1 的桌面上显示界面。

原来这就是 Windows 的 Session 0 隔离机制 ——服务运行在 Session 0,而用户交互桌面在 Session 1/2/…;Session 0 中创建的界面用户看不见,也无法接收用户输入。

解决方案思路:
考虑了项目的使用场景,最后决定编写一个“启动器”服务(例如 setExeSID.exe),它的职责不是自己显示界面,而是探测当前用户所在的 Session ID,然后使用 Windows API “穿透”隔离,将目标程序 getCode.exe 注入到用户的 Session 中运行。

大概逻辑为:

  1. setExeSID.exe 由 nssm 注册为服务,开机自启(因此它在 Session 0)。
  2. setExeSID.exe 获取当前活动用户的 Session ID(通常是 1,也可能是 2/3…)。
  3. setExeSID.exe 以该 Session 的用户 Token 为上下文,调用 CreateProcessAsUser 在用户会话中启动 getCode.exe(运行在用户桌面 winsta0\default)。

预期效果
仍然实现开机自启动(靠服务)
getCode.exe 实际运行在当前登录用户的 Session 中,可以正常显示 UI 并交互


2. 查看会话ID

打开“任务管理器” -> “详细信息”选项卡 -> 右键表头“选择列” -> 勾选“会话 ID”。

look_sid.jpg


3. Session 0 隔离(为什么服务无法交互)

机制

  • Windows XP 时代:系统服务和第一个登录的用户都运行在 Session 0。服务可以直接弹出窗口(MessageBox),用户能看到并交互。这带来了巨大的安全风险(例如 Shatter Attack,恶意程序向服务窗口发送消息提升权限)。
  • Windows Vista / 7 / 10 / 11:引入了 Session 0 隔离
    • Session 0专用于系统服务。非交互式,没有用户界面。
    • Session 1, 2…用户会话。第一个登录的用户分配 Session 1,远程桌面用户分配 Session 2 等。

为什么普通启动会失败?

当服务(Session 0)尝试启动一个带 UI 的进程时,该进程默认继承父进程的 Session ID (0)。由于 Session 0 无法访问用户的显卡驱动和桌面环境(winsta0\default),UI 渲染会失败,或者被系统拦截在不可见的后台。


4. 核心做法:如何穿透?

要实现从 Session 0 启动进程到 Session 1,不能使用简单的 CreateProcesssystem(),必须使用 Windows API 进行令牌(Token)操作。

关键步骤流程

  1. 定位目标:找到当前正在活动的(用户正在看的)Session ID (WTSGetActiveConsoleSessionId)。
  2. 获取令牌:拿到该 Session 中用户的身份令牌 (WTSQueryUserToken)。
  3. 复制令牌:将令牌复制一份,并转换为主令牌 (DuplicateTokenEx)。
  4. 处理环境:为新进程创建正确的环境变量块 (CreateEnvironmentBlock),否则程序可能找不到路径。
  5. 跨界启动:使用 CreateProcessAsUser 启动进程,并显式指定运行在交互式桌面 (winsta0\default)。

涉及到的主要 API

涉及到的主要 API 及其作用

  • GetCurrentProcess
    获取当前进程伪句柄(常用于配合 OpenProcessToken)。

  • OpenProcessToken
    打开进程访问令牌(Token),用于查询/调整权限。

  • LookupPrivilegeValue + AdjustTokenPrivileges
    为当前进程启用某些特权。
    常见原因:CreateProcessAsUser / 资源配额调整等需要特权。
    注意:前提是账户本身拥有该特权,只是默认未启用。

  • (会话枚举/定位)

    • WTSEnumerateSessions:枚举会话列表(可选)
    • WTSGetActiveConsoleSessionId:获取当前物理控制台活动会话(常用)
    • WTSQueryUserToken:获取指定 Session 的用户 Token(常用)
  • DuplicateTokenEx
    将 Token 复制为可用于创建进程的 Primary Token(常见必做步骤)。

  • CreateEnvironmentBlock
    为目标用户创建环境变量块(否则新进程可能继承服务的环境,导致路径/变量异常)。

  • CreateProcessAsUser
    以指定用户的安全上下文创建进程。
    关键点:默认新进程可能是非交互桌面,需要在 STARTUPINFO.lpDesktop 指定交互桌面。
    STARTUPINFO.lpDesktop = L"winsta0\\default"
    否则可能创建在不可见桌面,表现为“进程在跑但看不见/不能交互”。

5. 常见坑与注意事项

  1. 服务权限问题
    你的“启动器服务”必须以 LocalSystem (本地系统) 账户运行。如果以 LocalServiceNetworkService 运行,调用 WTSQueryUserToken 会直接返回 ERROR_PRIVILEGE_NOT_HELD (错误码 1314)。

  2. 无人登录时的情况
    如果电脑刚开机,停留在登录界面(LogonUI),此时 WTSGetActiveConsoleSessionId 可能返回 Session 1,但并没有即时用户。如果在此时启动程序,程序会在登录界面后台运行,用户输入密码进入桌面后可能反而看不到了。
    最好在服务中做一个轮询,检测到有真实用户登录(Session ID > 0 且状态为 Active)后再启动。

  3. 用户文件访问权限
    虽然进程在用户 Session 中运行,但如果涉及到读写特定的网络共享路径或加密文件夹,仍需注意令牌的权限范围。

5. 总结

当 Windows 上的程序需要:

  • 开机自启动(常由服务实现)
  • 同时又需要用户交互(UI/输入)

就必须考虑 Session 0 隔离。服务所在的 Session 0 与用户交互会话隔离,直接启动的 UI 程序往往不可交互。常见做法是:服务在 Session 0 中运行,引导在当前用户 Session 中创建进程(WTSQueryUserToken + CreateProcessAsUser),或采用服务与用户端代理的架构通过 IPC 完成交互。

这样就可以完美实现开机自启服务与用户桌面的无缝交互。(比如开发远程协助软件)

6. 参考资料

关于突破SESSION 0隔离创建进程

总结了用户权限设置和进程权限提升,提权demo也可参考

总结了window API

虚拟机安装官方树莓派系统,配置交叉编译链

1.下载官方镜像

https://www.raspberrypi.org/software/raspberry-pi-desktop/
通过官网,下载raspberry镜像64位系统iso文件之后,可以安装在虚拟机vbox/vmware里

2.下载交叉编译工具链

通过github下载最新的官方树莓派交叉编译工具链
git clone git://github.com/raspberrypi/tools.git

3.配置交叉编译工具链

将arm-bcm2708文件夹拷贝到/opt/arm-bcm2708下(自定义路径即可)

将上面到交叉编译工具链的路径配置到~/.bashrc文件

1
2
sudo vim ~/.bashrc
export PATH=$PATH:/opt/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin

4.测试交叉编译工具链是否安装成功

输入以下指令,如果有打印一些版本信息,那么说明交叉编译环境配置正确
arm-linux-gnueabihf-gcc -v
如图:
4b_succeed

5.假如第4步的时候有报错没有这个文件或目录,但实际是有这个文件的,可以apt-get安装libc6-dev-i386

如果报错提醒如下:
4b_fail_env

可以按照以下的解决方案尝试一下:
4b_deal_fail

然后重新输入arm-linux-gnueabihf-gcc -v即可发现打印版本信息

6.编写测试demo,然后编译生成执行文件

demo略,编译的指令如下:
4b_hello_world

需要注意的是,该可运行文件不能在PC机上运行,只能在树莓派arm板子上运行

总结

到此为止,虚拟机上的树莓派arm的交叉编译工具链搭建完成,这个官方的交叉编译工具链还是很靠谱的

参考资料

下载镜像参考以下网址
https://www.jianshu.com/p/1a65cb0b8f58
下载安装交叉编译链参考以下网址
https://www.cnblogs.com/zfyouxi/p/3831769.html

树莓派64位系统配置32位运行环境

配置libc6:armhf

树莓派本身安装了64位系统的情况下,需要配置32位程序的运行环境,首先安装依赖库,操作步骤如下,以下操作都要在root用户下进行
4b_armhf

配置32位程序的依赖库环境

1
git clone git://github.com/raspberrypi/tools.git

arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/路径下的lib库都拷贝到/usr/local/lib32路径下,这个路径可以自己创建,自定义路径名,专门用来存放底层32位依赖库(如libstdc++.so.6)

总结

其实最重要的,就是从官方提供的交叉编译工具链,把32位库给获取到,然后放到树莓派的自定义路径下,之后自己编译的32位执行程序/库,都要指定链接这个路径的基础库,否则会报错(找不到依赖库)

本身64位的树莓派系统是不带32位基础库的,所以必须从官方的交叉编译工具链里获取(目前,无法通过apt-get直接获取到所有的32位依赖库)

参考资料

https://raspberrypi.club/148.html

树莓派配置基础环境-ssh-root-静态ip

配置root用户

在终端进行如下操作即可:

1
sudo passwd root

然后根据提示输入root用户的密码
再重复输入一次刚刚设置的密码

切换root用户操作如下即可:

1
su -

输入设置的root用户的密码

配置开启ssh

操作如下即可:

1
sudo vim /etc/ssh/sshd-config

修改 PermitRootLogin yes
然后保存修改
然后执行sudo systemctl restart ssh
再通过ss -tnl查看是否开启成功即可

配置静态ip如下

在需要无线网络连接的情况下,配置eth0的静态ip如下:

1
sudo vim /etc/network/interfaces

然后再文件里增加以下内容

1
2
3
4
5
6
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 192.168.0.1 //IP地址
netmask 255.255.255.0 //掩码

然后保存
但是此时,如果不设置一下wlan0,那么会发现虽然静态ip设置成功了,但是树莓派却无法联网了
所以还要在interfaces文件里追加以下内容

1
2
3
auto wlan0
iface wlan0 inet dhcp
wpa_conf /etc/wpa_supplicant/wpa_supplicant.conf

然后保存
退出文件
重启树莓派即可

如图,配置动态IP

shu_ip_dhpc

如图,配置静态IP

shu_ip_static

linux下安装tcpdump并用其抓包

背景

有时候需要分析网络协议,这时候抓包看看直接的数据,能够有助于对协议有一个直观的感受,在windows下,可以直接安装Wireshark就能轻松抓包分析了,但是在linux下,没有Wireshark,所以可以安装tcpdump,用tcpdump抓包分析

安装tcpdump

安装tcpdump有两种方式,一种是下载tcpdump源码,然后编译安装;另一种是直接用系统安装命令

下载源码安装tcpdump

这个可以参考:https://blog.csdn.net/tic_yx/article/details/17012317
这篇文章里记录的很详细
文章内容截图如下:
源码编译tcpdump

直接用系统安装命令

在ubuntu下,可以直接使用sudo apt-get install tcpdump
如果这一步安装的时候有报错,可以更新一下下载源,国内清华的下载源还是很好用的

用tcpdump抓包

sudo tcpdump -i 网卡 -entXX
使用tcpdump抓包

也可以把tcpdump抓到的数据保存到文件里
sudo tcpdump -i 网卡 -entXX -w 文件名.pcap
tcpdump抓包后保存到文件里
然后将pcap文件从linux里拷贝到windows下,用Wireshark分析数据