分类 默认分类 下的文章

试着使用 MacPorts 提供的 cmake 命令从源码编译 Krita,但编译依赖就出现了 include、链接缺少符号等问题。原因是 MacPorts 的 cmake 包有个 patch,会优先从 MacPorts 的 prefix 下查找。

--- Modules/Platform/Darwin.cmake.orig
+++ Modules/Platform/Darwin.cmake
@@ -237,7 +237,9 @@
     endforeach()
   endif()
 endif()
-list(APPEND CMAKE_SYSTEM_PREFIX_PATH
-  /sw        # Fink
-  /opt/local # MacPorts
-  )
+
+# prepend the MacPorts prefix to CMAKE_SYSTEM_PREFIX_PATH
+# might already exist after this if the user has specified it
+list(INSERT CMAKE_SYSTEM_PREFIX_PATH 0
+  __PREFIX__
+)

无奈之下只能自己编译一个 cmake 了。如果你的 MacPorts 使用了默认的 /opt/local 作为 prefix,需要在解压缩 cmake 源码后修改 Darwin.cmake 去掉这行 /opt/local # MacPorts 再安装。

qt.qpa.plugin: Could not find the Qt platform plugin "cocoa" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

这是由于 MacPorts 的 qt5 不适用普通 macdeployqt 命令,打包时去掉这个命令就能正常启动了。

AddressSanitizer,简称 ASan,是一个非常实用的内存错误类 bug 检测工具。但在 macOS 上调试符号是不会直接写到二进制文件里的,想要获取带有行号的堆栈追踪十分麻烦。

以下是在没有调试符号的情况下 ASan 报出的堆栈追踪示例,可以看到函数名,但无法精确定位到文件和行号。

    #0 0x110a17ab0 in __sanitizer::internal_strlen(char const*)+0x10 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x5eab0)
    #1 0x1109dd87e in printf_common(void*, char const*, __va_list_tag*)+0x7ee (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x2487e)
    #2 0x1109ddae6 in wrap_vfprintf+0x66 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x24ae6)
    #3 0x10fe355d0 in v_do_log_to_file+0x260 (nvim:x86_64+0x1002cd5d0)
    #4 0x10fe35123 in logmsg+0x163 (nvim:x86_64+0x1002cd123)
    #5 0x1100f107b in tui_tk_ti_getstr+0xdb (nvim:x86_64+0x10058907b)

MacPorts 中开启 ASan 有 -fsanitize=address 就行,但想要获得更清晰的堆栈就复杂多了。

首先你需要在 Portfile 文件中添加以下几行,并确保编译过程有用到这些 flags。

configure.cflags    -O1 -g -fsanitize=address
configure.cxxflags  -O1 -g -fsanitize=address
configure.ldflags-append   -fsanitize=address -Wl,-object_path_lto,lto.o

-Wl,-object_path_lto,lto.o 用于保留 LTO 生成的临时文件,使用 -flto 链接时需要加到链接参数里。

修改完后安装时需要加 -s(从源码编译)和 -k(保留构建目录)选项,例如 sudo port -vsk install,否则无法生成调试用的 .dSYM 包。

安装完成后,就可以执行 dsymutil <binary> 在同目录下生成调试符号包 <binary>.dSYM。最后运行编译好的 <binary> 就能看到文件名:行号信息了。效果如下

    #0 0x101d3eab0 in __sanitizer::internal_strlen(char const*)+0x10 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x5eab0)
    #1 0x101d0487e in printf_common(void*, char const*, __va_list_tag*)+0x7ee (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x2487e)
    #2 0x101d04ae6 in wrap_vfprintf+0x66 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x24ae6)
    #3 0x1011575d0 in v_do_log_to_file log.c:327
    #4 0x101157123 in logmsg log.c:151
    #5 0x10141307b in tui_tk_ti_getstr tui.c:2155

测试完后记得删除 .dSYM 目录,避免日后二进制文件更新导致调试符号对不上。

参考资料

https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports

Note that on macOS you may need to run dsymutil on your binary to have the file:line info in the AddressSanitizer reports.

https://clang.llvm.org/docs/CommandGuide/clang.html

On Darwin, when using -flto along with -g and compiling and linking in separate steps, you also need to pass -Wl,-object_path_lto,<lto-filename>.o at the linking step to instruct the ld64 linker not to delete the temporary object file generated during Link Time Optimization (this flag is automatically passed to the linker by Clang if compilation and linking are done in a single step). This allows debugging the executable as well as generating the .dSYM bundle using dsymutil(1).

IPv6 relay 模式怎么配置网上有一大把教程,就不多说了。重点在于为什么客户端能获取地址却 ping 不通公网,但 ping 一下路由器的 wan IPv6 地址后就可以上网了。通过在客户端设备上抓包和查看 ip neigh(或 ndp -an)可以发现,区别在于 IPv6 的邻居发现过程。

正确方式

IP6 fe80::xxxx > [solicited-node multicast address]: ICMP6, neighbor solicitation, who has [client address], length 32
IP6 fe80::yyyy > fe80::xxxx: ICMP6, neighbor advertisement, tgt is [client address], length 32

错误方式(路由器在 lan 侧找不到客户端 IP)

IP6 fd..:[ULA-prefix address] > [solicited-node multicast address]: ICMP6, neighbor solicitation, who has [target address], length 32

为什么错误方式下邻居发现会失败呢?原因是纯 relay 模式下客户端不会获取到 ULA-prefix1 下的地址/路由/邻居信息,当客户端尝试回复从路由器发出的 ULA-prefix 下地址的 neighbor solicitation 时,匹配不到这个 ULA 地址的路由只好放弃。

那么为什么 ping 一下网又通了呢?这是因为 odhcpd 从 wan 那边获取到了客户端的信息,从而配置上了 NDP 代理和路由。将 odhcpd 的 loglevel 配置成 7(LOG_DEBUG,数字来自syslog(3))后可以看到对应的 proxy 日志。

所以只要去掉路由器 lan 上的 ULA 就可以了,也就是在 network 配置中注释掉 ip6assign。重启路由器后,客户端就可以正常上网了。

另一种可能的方式是同时启用 lan 上 ULA 和 relay 的地址分配,不过 relay 模式下不好实现。

最后剩下一个问题,中继模式下首次请求延迟会比较高,之后就正常了,这部分原理可以参考 https://blog.icpz.dev/articles/notes/odhcpd-relay-mode-discuss/

参考资料

https://openwrt.org/docs/guide-user/network/ipv6/start#ipv6_relay

# cat /etc/config/dhcp
config dhcp lan
    option dhcpv6 relay
    option ra relay
    option ndp relay
    ...
 
config dhcp wan6
    option dhcpv6 relay
    option ra relay
    option ndp relay
    option master 1
    option interface wan6

笔者注:如非必要建议关闭 dhcpv6,原因见 https://issuetracker.google.com/issues/36949085


https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1

Solicited-Node multicast address are computed as a function of a
node's unicast and anycast addresses. A Solicited-Node multicast
address is formed by taking the low-order 24 bits of an address
(unicast or anycast) and appending those bits to the prefix
FF02:0:0:0:0:1:FF00::/104 resulting in a multicast address in the
range

     FF02:0:0:0:0:1:FF00:0000

to

     FF02:0:0:0:0:1:FFFF:FFFF

https://openwrt.org/docs/guide-user/base-system/basic-networking

network.globals.ula_prefix='fd27:70fa:5c1d::/48'

https://datatracker.ietf.org/doc/html/rfc4861#section-7.2.4

If the source of the solicitation is the unspecified address, the
node MUST set the Solicited flag to zero and multicast the
advertisement to the all-nodes address. Otherwise, the node MUST set
the Solicited flag to one and unicast the advertisement to the Source
Address of the solicitation.


P.S. Hybrid 模式是什么?其实就是智能开启 relay,代码如下,不要误以为是同时开启 relay 和 server 模式。

            /* Resolve hybrid mode */
            if (i->dhcpv6 == MODE_HYBRID)
                i->dhcpv6 = (master && master->dhcpv6 == MODE_RELAY) ?
                        MODE_RELAY : MODE_SERVER;

            if (i->ra == MODE_HYBRID)
                i->ra = (master && master->ra == MODE_RELAY) ?
                        MODE_RELAY : MODE_SERVER;

            if (i->ndp == MODE_HYBRID)
                i->ndp = (master && master->ndp == MODE_RELAY) ?
                        MODE_RELAY : MODE_DISABLED;

odhcpd 的 README 里有这么一段,说明客户端地址在路由器上的路由是它配置的。

4. Proxy for Neighbor Discovery messages (solicitations and advertisements)
   a) support for auto-learning routes to the local routing table
   b) support for marking interfaces "external" not proxying NDP for them
      and only serving NDP for DAD and for traffic to the router itself
      [Warning: you should provide additional firewall rules for security]