在 Windows 上用 MinGW 搭建 lwIP 调试环境:方法与踩坑实录

185 阅读17分钟

在 Windows 上用 MinGW 搭建 lwIP 调试环境:方法与踩坑实录

目标:在 Windows 上把 lwIP 协议栈跑起来,作为学习 TCP/IP 和嵌入式网络开发的模拟环境。 本文只讲"搭建 + 排坑",TCP 通信验证和 Wireshark 抓包分析留到下一篇。

全文分三部分:准备(环境清单、路线选择、项目结构)→ 编译配置(构建方法与两个编译坑)→ 网络配置lwipcfg.h 最终配置一览 + 四个网络坑的原理与调试方法)。

第一部分:准备

1.1 为什么在 Windows 上跑 lwIP

lwIP 是为嵌入式系统设计的轻量级 TCP/IP 协议栈,核心代码(src/)是平台无关的纯 C。它通过"移植层"抽象出线程、定时器、网卡收发等能力——这意味着同一份协议栈代码,既能烧进单片机,也能跑在 Windows 上。

在 Windows 上跑起来的整体结构是:

应用程序 (example_app)
   ↓
lwIP 协议栈核心 (src/core, 和嵌入式设备上是同一份代码)
   ↓
win32 移植层 (contrib/ports/win32: sys_arch.c 映射 Win32 线程, pcapif.c 收发帧)
   ↓
Npcap (绕过 Windows 自己的协议栈, 直接读写网卡上的原始以太网帧)
   ↓
物理网卡 ↔ 路由器/局域网

关键点:lwIP 通过 Npcap 绕过 Windows 自己的 TCP/IP 栈,用自己的 MAC 和 IP 独立收发。对路由器来说,这就是接在同一根线上的另一台设备。所以在 Windows 上学到的协议行为(DHCP 状态机、TCP 握手、重传),可以直接迁移到真实嵌入式项目。

1.2 环境清单

目录位置仅供参考

项目版本/位置说明
OSWindows 11 (10.0.26200) x64
编译器MSYS2 UCRT64 gcc 16.1.0C:\msys64\ucrt64\bin\gcc.exe
构建工具cmake 4.4.0 + ninja同在 MSYS2 中
lwIP 源码2.2.2 (git 仓库)src/ + contrib/
Npcap 运行时已安装C:\Windows\System32\wpcap.dll
Npcap SDK1.16, D:\npcap-sdk-1.16Include/pcap.hLib/x64/wpcap.lib

各组件来源:

  • lwIP 源码:git指令:git clone https://github.com/lwip-tcpip/lwip.git(GitHub 仓库是官方镜像,主仓库托管在 savannah.nongnu.org,两边内容同步)
  • MSYS2www.msys2.org 下载安装包。装完后在 UCRT64 终端(直接在搜索栏搜索MSYS2 UCRT64,别用错了),用 pacman 安装工具链:pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja
  • Npcap installer + SDK:都在 npcap.com 的下载页——运行时选 Npcap installer(装 Wireshark 时一般会带上),SDK 是单独的压缩包(免费下载,许可允许个人学习用途),解压出来就是上表里的 npcap-sdk-1.16 目录
  • Wireshark(观察协议时用):www.wireshark.org ,安装过程中会提示安装 Npcap

最容易搞混的一点:Npcap 运行时 ≠ Npcap SDK。 运行时是让程序能用网卡抓包的驱动(装 Wireshark 时一般会带上);SDK 是编译用的头文件和 .lib,需要单独从 npcap.com 下载。两个都要。

老旧教程避坑: 很多老教程使用的 WinPcap已停止维护。虽然可从其官网下载WpdPack开发包(例如4.1.2版本)。 但是这里不建议使用,毕竟现在wireshark会主动帮你安装新版Npcap,使用WinPcap可能造成错误。

关于终端:建议用 MSYS2 安装时自带的 UCRT64 终端,gcc/cmake/ninja 安装后开箱可用,但要保证 环境变量PATH 里有 C:\msys64\ucrt64\bin

1.3 路线选择:MinGW 还是 MSVC

lwIP 的 win32 移植提供两套构建入口: contrib/ports/win32/msvc/ 下的 Visual Studio 工程,和 CMake 文件。我的机器没装老版本的 Visual Studio(这个我跟着博客试过,因为版本不兼容问题难以成功配置),但有 MSYS2 的 gcc,所以选了 MinGW gcc + CMake + Ninja 路线——不用装几个 GB 的 VS也不用特意找到老版本,立刻能走通。本文使用这条路线。

1.4 开工前:项目结构和三层配置体系

先花两分钟看清 lwIP 仓库的结构:

lwip/
├── src/          # 协议栈核心, 平台无关 (烧进单片机的就是这份代码)
│   ├── core/         # TCP/UDP/IP/ICMP/DHCP 等协议实现
│   ├── api/          # socket / netconn API
│   └── include/      # 头文件, 含 lwip/opt.h
├── contrib/      # 移植层和示例 (不进单片机, 只在 PC 模拟时用)
│   ├── ports/win32/          # Windows 移植: 线程适配 + Npcap 网卡驱动
│   ├── examples/example_app/ # 示例应用和它的配置
│   └── apps/                 # 附加应用 (ping, socket_examples 等)
└── test/ doc/    # 测试和文档, 本篇不涉及

三层配置体系是本篇所有"改配置"动作的总地图:

层级文件作用改不改
默认值层src/include/lwip/opt.h定义全部编译期选项的默认值(协议开关、内存池大小、超时……)永远不改,要覆盖去下一层
协议栈配置层contrib/examples/example_app/lwipopts.h覆盖 opt.h 的默认值,比如打开 DHCP_DEBUG 调试输出3.4 节打开 DHCP 日志改的就是这里
应用配置层contrib/examples/example_app/lwipcfg.hexample_app 的私有配置:选哪块网卡、MAC、静态 IP、DHCP 开关、跑哪些示例应用本篇改得最多的文件,需要根据个人情况进行配置

关于lwipcfg.h 值得单独说一句:它不是 lwIP 核心的一部分,只是 example_app 这个示例的配置(从同目录的 lwipcfg.h.example 复制而来,注意文件名里没有 "opts")。真实嵌入式项目里你会写自己的这一层。它管的都是"这次模拟要怎么跑":网卡序号/GUID、MAC 和 IP、USE_DHCP/USE_AUTOIP 开关、LWIP_PING_APP 等应用开关——这些改动全部集中在 3.1 节讲。

本篇动过的文件清单(全部改动控制在 4 个文件内):

文件属于哪层改了什么章节
lwipcfg.h应用配置网卡 GUID、MAC、静态 IP、关 DHCP/AutoIP、开 ping 应用3.1
lwipopts.h协议栈配置打开 DHCP_DEBUG(排查用)3.4
contrib/ports/win32/Filelists.cmakewin32 移植构建脚本Npcap SDK 按 SYSTEM 包含、精确压制告警2.3
src/include/lwip/errno.h核心代码(唯一动的)#undef 补丁解决 errno 重定义2.2

另有两个文件没改但值得知道位置:contrib/ports/win32/include/arch/cc.h 是编译器适配层,2.2 节排查 errno 冲突时的关键现场; contrib/ports/win32/pcapif.c 是基于 Npcap 的网卡驱动,3.1 节"MAC 最后一字节 +1"的规则就出自它。

第二部分:编译配置

2.1 构建的正确姿势(兼驳网上教程的两个错误)

正确流程(在 lwIP 仓库根目录下):

# 复制示例配置(一次性),这一步一定提前做,否则cmake无法通过
cp contrib/examples/example_app/lwipcfg.h.example \
   contrib/examples/example_app/lwipcfg.h

mkdir build && cd build
cmake .. -G Ninja -DWPDPACK_DIR='D:/npcap-sdk-1.16' -DCMAKE_BUILD_TYPE=Debug
cmake --build .

成功后生成 build/contrib/ports/win32/example_app/example_app.exe。 (-DCMAKE_BUILD_TYPE=Debug 让编译带调试符号——本篇搭的是"调试环境", 没有它后面 gdb/VS Code 断点一个都命不中,别省。)

错误教程 1:直接对 example_app 目录配置

有些博客让你这样:

cd lwip/contrib/ports/win32/example_app
mkdir build && cd build
cmake .. -G "MinGW Makefiles" ...

对 lwIP 2.2.x 不适用。 打开 contrib/ports/win32/example_app/CMakeLists.txt 第一行就是 include(${LWIP_DIR}/contrib/ports/CMakeCommon.cmake)——它没有 project()LWIP_DIR 要靠顶层 CMakeLists.txt 通过 add_subdirectory() 传入。直接对它配置必败。这类博客多半针对旧版本或改过的工程。

错误教程 2:用 CMAKE_PREFIX_PATH 指定 pcap SDK

cmake .. -DCMAKE_PREFIX_PATH=C:/npcap-sdk ...

传了也白传。contrib/ports/win32/Filelists.cmake:Windows 分支下 wpcap 库路径是硬编码的 ${WPDPACK_DIR}/lib/x64/wpcap.lib,只认 -DWPDPACK_DIR=,从不查询 CMAKE_PREFIX_PATH

教训:博客/AI/文档给的方案,必须对着本地这份源码验证。源码永远是最终事实。

2.2 编译坑 1:errno 重定义(EWOULDBLOCK / ETIMEDOUT)

现象

第一批编译错误,十几个文件报同一个错:

src/include/lwip/errno.h:88:10: error: 'EWOULDBLOCK' redefined [-Werror]
   88 | #define  EWOULDBLOCK     EAGAIN  /* Operation would block */
C:/msys64/ucrt64/include/pthread_compat.h:108:9: note: this is the location of the previous definition
  108 | #define EWOULDBLOCK     140

排查

上面"现象"里贴的 error 行和 note 行,其实是同一条报错的两个部分;这是当时 src/core/init.c 的完整报错原文:

In file included from src/include/lwip/sockets.h:54,
                 from src/core/init.c:47:
src/include/lwip/errno.h:88:10: error: 'EWOULDBLOCK' redefined [-Werror]
   88 | #define  EWOULDBLOCK     EAGAIN  /* Operation would block */
In file included from C:/msys64/ucrt64/include/pthread_time.h:27,
                 from C:/msys64/ucrt64/include/time.h:329,
                 from C:/msys64/ucrt64/include/sys/time.h:10,
                 from contrib/ports/win32/include/arch/cc.h:55,
                 from src/include/lwip/arch.h:48,
                 from src/include/lwip/debug.h:40,
                 from src/include/lwip/opt.h:52,
                 from src/core/init.c:38:
C:/msys64/ucrt64/include/pthread_compat.h:108:9: note: this is the location of the previous definition
  108 | #define EWOULDBLOCK     140

每条链自下而上读就是"谁拉进了谁"。如果报错里没带链(比如 Ninja 并行输出被截断),还有个专门的工具:gcc -H 会把每个源文件的 include 树按缩进打出来,冲突双方分别在树的哪个分支一目了然。整理后的对峙图:

init.c:47 → sockets.h → lwip/errno.h:88                                (EWOULDBLOCK=EAGAIN=11)
init.c:38 → opt.h → debug.h → arch.h → cc.h:55 → <sys/time.h> → ... → pthread_compat.h (EWOULDBLOCK=140)

contrib/ports/win32/include/arch/cc.h 里,非 MSVC 编译器一律 #define LWIP_PROVIDE_ERRNO(为老编译器兜底的历史假设),于是 lwIP 定义自己的 errno;而 MinGW 的 <sys/time.h> 又拉进了 pthread_compat.h,两边定义值还不一样,-Werror 下直接失败。

弯路:改用系统 errno

第一反应是让 MinGW 走系统 errno(cc.h 改定义 LWIP_ERRNO_STDINCLUDE)。编译确实过了,但后来发现这是埋雷:lwIP 的应用层代码假设 EWOULDBLOCK == EAGAIN(Linux 下二者都是 11,比如 contrib/apps/socket_examples/socket_examples.c 里就有 LWIP_ASSERT("errno == EAGAIN", ...)),而 UCRT64 里 EAGAIN=11EWOULDBLOCK=140,不相等——用到 socket API 时断言就会炸。

正解:保留 lwIP 自带 errno,用 #undef 消冲突

lwIP 自带定义里 EWOULDBLOCK == EAGAIN,和上游假设一致。不能用它的唯一原因是和系统头重定义,那就先 #undef 再定义。在 src/include/lwip/errno.h 打两行补丁:

#ifdef EWOULDBLOCK
/* some C libraries (e.g. MinGW-w64/UCRT64) define EWOULDBLOCK != EAGAIN;
 * undefine it so that lwIP's own definition (== EAGAIN) takes effect */
#undef EWOULDBLOCK
#endif
#define  EWOULDBLOCK     EAGAIN  /* Operation would block */

注意:这段补丁必须放在#define EWOULDBLOCK EAGAIN之前!

ETIMEDOUT 同样处理。冲突的两个宏之外,其余 errno 值双方一致,互不影响。这个补丁改动最小、语义最正确。(这里也有教程采用的是改变包含顺序,但是这里个人感觉直接用#undef更加安全)

2.3 编译坑 2:Npcap SDK 头文件触发 -Werror

现象

D:/npcap-sdk-1.16/include/npcap-bpf.h:128:22: error: C++ style comments are incompatible with C90
D:/npcap-sdk-1.16/include/npcap-defs.h:131:1: error: redundant redeclaration of '__C_ASSERT__'
contrib/ports/win32/pcapif_helper.c:43:7: error: 'hFile' is deprecated

lwIP 的 CMake 给自己的代码配了极严格的告警(-Wall -pedantic -Werror -Wc90-c99-compat ...),第三方 Npcap 头文件根本扛不住。

解法:自己的代码和第三方代码,用不同标准对待

修改 contrib/ports/win32/Filelists.cmake,原来是:

add_library(lwipcontribportwindows EXCLUDE_FROM_ALL ${lwipcontribportwindows_SRCS})
target_include_directories(lwipcontribportwindows PRIVATE ${LWIP_INCLUDE_DIRS} "${WPDPACK_DIR}/include" ${LWIP_MBEDTLS_INCLUDE_DIRS})
target_compile_options(lwipcontribportwindows PRIVATE ${LWIP_COMPILER_FLAGS})

改为:

add_library(lwipcontribportwindows EXCLUDE_FROM_ALL ${lwipcontribportwindows_SRCS})
target_include_directories(lwipcontribportwindows PRIVATE ${LWIP_INCLUDE_DIRS} ${LWIP_MBEDTLS_INCLUDE_DIRS})
# 第三方 SDK 头文件按 SYSTEM 包含: 不适用 lwIP 的严格告警
target_include_directories(lwipcontribportwindows SYSTEM PRIVATE "${WPDPACK_DIR}/include")
target_compile_options(lwipcontribportwindows PRIVATE ${LWIP_COMPILER_FLAGS}
    # port 代码访问了 SDK 标记为 deprecated 的内部成员, 只压这一个告警
    $<$<C_COMPILER_ID:GNU>:-Wno-deprecated-declarations>)

两个原则:

  • SYSTEM 包含目录:告诉编译器"这是第三方头,别用我的标准要求它"。SDK 头文件不归我们管,不可能去改它
  • 压制告警要精确到点:只关 -Wno-deprecated-declarations 这一个,而不是 -w 或删 -Werror——大面积关告警是掩盖问题,lwIP 自己的代码继续受严格约束

有些博客教你注释掉 -Wc90-c99-compat、全局加 -Wno-unused-parameter,那是把lwIP对自己代码的纪律也一起废了,不建议。

第三部分:网络配置

3.1 lwipcfg.h 最终配置一览

网络部分的配置全在 contrib/examples/example_app/lwipcfg.h 一个文件里。先把最终能跑通的完整配置集中给出来——方便各位参照配置;每个值为什么这么填、怎么查出自己机器上的值,见后续各小节的排坑实录。

/* ===== lwipcfg.h 网络相关最终配置(值要换成你自己机器的) ===== */

/* 1. 网卡选择:序号 + GUID 双保险(为什么 → 3.2 节) */
#define PACKET_LIB_ADAPTER_NR         5
#define PACKET_LIB_ADAPTER_GUID       "D83D00A3-B69D-4368-ABC0-9B1AAB9F4E61"

/* 2. MAC:用有线网卡的真实 MAC。注意 pcapif.c 会把基准 MAC 最后一字节
 *    加上 netif->num(多网卡防冲突,my_mac_addr[5] += netif->num),
 *    我的真实 MAC 末字节是 8F、网卡 num=1,所以基准要填 8E,+1 后落回 8F */
#define LWIP_MAC_ADDR_BASE  {0x38,0xA7,0x46,0x0F,0x97,0x8E}

/* 3. 静态独立 IP,关掉 DHCP/AutoIP(为什么 → 3.4、3.5 节) */
#define USE_DHCP    0
#define USE_AUTOIP  0
#define LWIP_PORT_INIT_IPADDR(addr)   IP4_ADDR((addr), 192,168,0,200)
#define LWIP_PORT_INIT_GW(addr)       IP4_ADDR((addr), 192,168,0,1)
#define LWIP_PORT_INIT_NETMASK(addr)  IP4_ADDR((addr), 255,255,255,0)

/* 4. 打开自带的 ping 应用,验证用(目标默认是网关) */
#define LWIP_PING_APP  1

三个查询/确认动作:

  • GUID 和真实 MAC:PowerShell 里 Get-NetAdapter | Select-Object Name, InterfaceGuid, MacAddress, Status
  • 静态 IP:选路由器网段里空闲的地址,改之前先 ping 一下确认没人用
  • 序号:直接运行一次 example_app.exe,它会枚举本机网卡列表(下节就讲这个列表怎么读)

注意每次改完一定重新 cmake --build . 再运行!!!。下面是配置过程中建立的四个"应有认识",以及对应的调试方法。

3.2 应有认识 1:网卡选择——序号会漂移,GUID 才可靠

编译通过后直接运行 example_app.exe,它会先枚举本机网卡,像是:

 0: NPF_{9198ECB8-...}   Desc: "WAN Miniport (Network Monitor)"
 1: NPF_{47D94BE7-...}   Desc: "WAN Miniport (IPv6)"
 2: NPF_{5696E8A8-...}   Desc: "WAN Miniport (IP)"
 3: NPF_{A63E4EA8-...}   Desc: "Bluetooth Device (Personal Area Network)"
 4: NPF_{054CEC85-...}   Desc: "Killer(R) Wi-Fi 6E AX1675i ..."
 5: NPF_{D83D00A3-...}   Desc: "Killer E3100G 2.5 Gigabit Ethernet Controller"
 6-7: ...                Desc: "Microsoft Wi-Fi Direct Virtual Adapter ..."
 8: NPF_Loopback         Desc: "Adapter for loopback traffic capture"

这个列表怎么读:序号就是 lwipcfg.hPACKET_LIB_ADAPTER_NR 要填的值。0-2 是 Windows 的 VPN/PPPoE 虚拟协议驱动,3 是蓝牙,6-7 是 Wi-Fi Direct 虚拟适配器——都不能用。只有 4(物理无线)和 5(物理有线)是真实网卡。默认配置选的是序号 1(WAN Miniport),一块废卡,所以起来后 IP 永远是 0.0.0.0。(你自己的未必和我相同,仅供参考)

别只填序号,要用 GUID 双保险:序号会因为增删设备而漂移——我插上网线后有线网卡从序号 8 变成了 5。GUID 是 Windows 给每块网卡的唯一 ID,查法见 3.1 节。程序启动时按 GUID 找到网卡,还会自动纠正序号打印出 Using adapter_num: 5(也就是说其实这里你也不必一定配置正确数字)——序号漂移从此无感。

3.3 应有认识 2:假 MAC 行不通——根源在"网卡把哪些帧交上来"

一句话总结:要用真实 MAC(本节),要用静态独立 IP(3.5 节)!!

lwipcfg.h 默认给 lwIP 配一个编造的 MAC(LWIP_MAC_ADDR_BASE {0x00,0x01,...})。它能不能用,取决于收包路径上的每个环节是否愿意把"目的 MAC ≠ 自己"的帧投递给 lwIP:

  • 有线:看网卡。 支持混杂模式的网卡会把所有帧交上来,假 MAC 通常没事;但我这块 Killer E3100G 只投递发给自己 MAC 的单播帧——实测假 MAC 下 DHCP 同样拿不到地址(3.4 节的 169.254,其实就是"有线 + 假 MAC"的产物)
  • Wi-Fi:理论上必败。 802.11 要求帧的源 MAC 必须是已关联到 AP 的站点 MAC(即网卡真实 MAC),Npcap 在 Wi-Fi 上做"伪以太网"转换会把发出帧的源 MAC 改写成真实 MAC;而 lwIP 自认的是假 MAC,回包比对不认,丢弃;AP 侧按假 MAC 回包也因未关联而丢

所以无论有线还是无线,MAC 都要填网卡真实值(这就是 3.1 配置里第 2 项的来历)。

至于 Wi-Fi:我把真实 MAC 配置上后实测了一次,依然不通(发得出、收不回),障碍在 Npcap/驱动更底层。建议直接用有线网线,别在 Wi-Fi 上纠缠。

3.4 应有认识 3:169.254.x.x 不是 DHCP 成功

换有线后(此时跑的还是假 MAC,这正是下面 DHCP 收不到回应的根因,见 3.3),等了很久,输出终于出现了第三行:

status_callback==UP, local interface IP is 169.254.7.4

我以为我成功了,但……别高兴太早——169.254.x.x 是 AutoIP 链路本地地址,意思是 DHCP 一直没拿到租约,lwIP 的 AutoIP 兜底机制自己编了一个。真正的 DHCP 成功应该是路由器网段的地址(如 192.168.0.x)。

分段定位法:打开 DHCP_DEBUG

为了解决这个问题,我们可以用分段定位法,在 contrib/examples/example_app/lwipopts.h 里把 DHCP_DEBUG 后面改成 LWIP_DBG_ON,重编译运行,日志一目了然:

dhcp_discover()
dhcp_discover: sendto(DISCOVER, IP_ADDR_BROADCAST, LWIP_IANA_PORT_DHCP_SERVER)
dhcp_discover(): set request timeout 2000 msecs
dhcp_fine_tmr(): request timeout
dhcp_discover()
... (2s4s8s16s60s 指数退避重试, 全程没有一条 dhcp_recv)

判读逻辑(协议调试的通用套路):有 discover 但永远没 recv → 发得出去、收不回来,问题在收包路径或对方不理睬;如果连 discover 都没有 → lwIP 内部问题。(当然,如果你遇到了其他问题可以把对应的选项改成ON来进行观察

3.5 应有认识 4:同机 ping 不通,是原理性限制

排查过程中自然想"从 Windows ping 一下 lwIP 试试",结果永远不通。折腾一圈后确认:这不是 bug,是以太网的工作原理

交换机的基本规则——帧不会被发回它的入端口(hairpin 过滤)。Windows 发出"谁是 192.168.0.200"的 ARP 广播,交换机泛洪到除了这个网口之外的所有端口,同一块网卡上的 lwIP 收不到;就算 lwIP 回应,回应帧的目的 MAC 就在入端口上,交换机照样过滤。同一台机器上的两个协议栈,无法通过物理链路互相通信。

注意两个报错文案的区别,信息量很大:

  • 无法访问目标主机:ARP 解析失败(链路层就不通)
  • 请求超时:ARP 成功了,但 ICMP 没回应(问题在更高层)

正解

配置层面按 3.1 节来就行——真实 MAC 加静态独立 IP、关掉 DHCP/AutoIP,避免 lwIP 和 Windows 同 MAC 拿到同 IP 互相干扰。验证层面记住一条:要用第三方设备(lwIP 自己 ping 网关,或局域网里另一台机器 ping lwIP),别用本机。

3.6 跑通

结果日志:

status_callback==UP, local interface IP is 192.168.0.200
ping: send 192.168.0.1
ping: recv 0:0:0:0:0:ffff:c0a8:1 16 ms

ping: recv 后面那个怪地址是 IPv4-mapped IPv6 格式的显示:c0a8:1 = c0.a8.00.01 = 192.168.0.1(启用了 IPv6 的构建里,lwIP 内部统一用双格式 ip_addr_t,打印走了 IPv6 路径,纯显示问题)。

附:git 管理教训(真实踩坑,代价半天)

搭建过程中我在 git 上栽了两跤,值得记下来:

  1. 冲突解决后没编译就提交:解决冲突时残留了一个孤字符 /(在 #endif 行尾),提交后编译直接报 '/*' within comment。铁律:冲突解决完,先编译,再提交。
  2. "格式化提交"静默回退了逻辑:一个标题写着"调整目标地址为 IPv4 映射地址"的提交(这个我让ai帮我起的名),实际 diff 却把这个调整改回了原值,还顺手做了 100 多行空格调整。提交信息与实际内容完全相反。铁律:git commit 前必须 git diff --staged 过一遍。

事后清理用的是 git reset --hard <最后一个健康提交>——因为坏提交只在本地没推送;如果已经 push 共享了,要用 git revert(新增反向提交)而不是改写历史。

总结与预告

至此,lwIP 已经在 Windows 上作为一台"独立设备"跑起来了:有自己的 IP(192.168.0.200),能和路由器双向通信。全程改动控制在 4 个文件内,其中只有 lwip/errno.h 两行涉及核心代码。

回顾最有价值的三个认知:

  • 报错归类 + 顺 include 链找冲突双方,比逐条看错误快得多
  • 自己的代码和第三方代码用不同告警标准(SYSTEM 包含),压制告警精确到点
  • 网络不通先分层定位(DHCP 日志 → ARP → ICMP),并警惕"测试方法本身不成立"(同机 ping)

下一篇:《在 Windows 上跑 lwIP(二):TCP 客户端实测与 Wireshark 逐包分析》(socket_examples 的适配与三个测试的含义),以及用 Wireshark 逐包解剖三次握手、数据传输和四次挥手。

致谢

这是我的第一篇博客,多谢有老师和其他乐于分享的伙伴们帮助,有不对的地方希望大家积极指正。


环境:Windows 11 / MSYS2 UCRT64 gcc 16.1.0 / lwIP 2.2.2 / Npcap SDK 1.16