0%

在 Ubuntu 22.04 上编译以下使用 OpenSSL 库的程序时,发生了 undefined reference to `OPENSSL_init_crypto' 这一链接错误:

1
2
3
4
5
#include <openssl/evp.h>

int main() {
OPENSSL_add_all_algorithms_noconf();
}
1
2
3
4
$ gcc -lssl -lcrypto foo.c
/usr/bin/ld: /tmp/ccHvEUdz.o: in function `main':
foo.c:(.text+0x13): undefined reference to `OPENSSL_init_crypto'
collect2: error: ld returned 1 exit status

与此同时,我在一个干净的 NixOS 系统中尝试了编译同一个文件,是能成功的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
description = "A very basic flake";

inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};

outputs =
{ self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
in
{
devShells.${system}.default = pkgs.mkShell {
buildInputs = with pkgs; [
openssl
];

nativeBuildInputs = with pkgs; [
gcc11
pkg-config
];
};
};
}
1
2
3
4
NixOS$ pkg-config --cflags --libs openssl
-I/nix/store/xm6d1ig9ff7zkid1gvzsbig45m2pnlaz-openssl-3.3.2-dev/include -L/nix/store/m8gwqmn8k3jm0gbcia358mz4y00lgmbc-openssl-3.3.2/lib -lssl -lcrypto
NixOS$ gcc $(pkg-config --libs openssl) foo.c
NixOS$ ./a.out

为了排查问题,首先确认相关库的版本是否正确。注意在 Ubuntu 22.04 上,OpenSSL 的打包粒度很细,我把相关的包都重装了一遍(实际上还做了一堆别的检查,从略):

1
# apt reinstall openssl libssl3 libssl-dev

接下来确认使用的 linking flags 是否正确:

1
2
$ pkg-config --cflags --libs openssl
-lssl -lcrypto

可见我使用的编译参数与 pkg-config 推荐的一样。接下来我们查看 GCC 实际使用的 .so 文件是什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ gcc --verbose -Wl,--verbose foo.c
(...略...)
/usr/bin/ld: mode elf_x86_64
(...略...)
attempt to open /usr/lib/gcc/x86_64-linux-gnu/11/libssl.so failed
attempt to open /usr/lib/gcc/x86_64-linux-gnu/11/libssl.a failed
attempt to open /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/libssl.so succeeded
/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/libssl.so
attempt to open /usr/lib/gcc/x86_64-linux-gnu/11/libcrypto.so failed
attempt to open /usr/lib/gcc/x86_64-linux-gnu/11/libcrypto.a failed
attempt to open /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/libcrypto.so succeeded
/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/libcrypto.so
(...略...)
/usr/bin/ld: /tmp/ccJpwMbq.o: in function `main':
foo.c:(.text+0x13): undefined reference to `OPENSSL_init_crypto'
/usr/bin/ld: link errors found, deleting executable `a.out'
collect2: error: ld returned 1 exit status

可见 ld 实际使用的是 /usr/lib/x86_64-linux-gnu/lib{ssl,crypto}.so 两个文件。我们使用 nm -D 命令查看相关符号是否存在:

1
2
3
4
$ nm -D /usr/lib/x86_64-linux-gnu/libssl.so | rg OPENSSL_init_crypto
U OPENSSL_init_crypto@OPENSSL_3.0.0
$ nm -D /usr/lib/x86_64-linux-gnu/libcrypto.so | rg OPENSSL_init_crypto
00000000001bf600 T OPENSSL_init_crypto@@OPENSSL_3.0.0

根据 nm(1p)

1
2
3
4
"T"
"t" The symbol is in the text (code) section.

"U" The symbol is undefined.

可见 libcrypto.so 中确实定义了 OPENSSL_init_crypto 这一符号。这可奇了,为什么 ld 就是找不到它呢?

经过痛苦而无益的尝试,小编突然发现,只要把链接参数和源文件的顺序调转一下,就能在 Ubuntu 上成功编译了:

1
2
3
4
5
6
$ gcc -lssl -lcrypto foo.c
/usr/bin/ld: /tmp/ccIqp0ls.o: in function `main':
foo.c:(.text+0x13): undefined reference to `OPENSSL_init_crypto'
collect2: error: ld returned 1 exit status
$ gcc foo.c -lssl -lcrypto
$ ./a.out

相对地,在 NixOS 上,参数的顺序并无影响:

1
2
NixOS$ gcc $(pkg-config --libs openssl) foo.c
NixOS$ gcc foo.c $(pkg-config --libs openssl)

其实这一切背后的原因与 --{,no-}as-needed 这一组链接参数有关。具体的细节在 Why does the order in which libraries are linked sometimes cause errors in GCC? - Stack Overflow 这个 Stack Overflow 回答里科普得很清楚,就不多介绍啦。

需要注意的是,这两个参数的影响除了编译成功与否之外,还反映在最终的可执行文件上:如果实际上没有引用库的内容,则 --as-needed 实际上不会将可执行文件链接过去(可以使用 ldd 确认)。

由此可见,虽然 pkg-config 确实能给出(推荐的)编译参数,但怎样把这些编译参数正确地整合到编译命令里(对于没有经验的人来说)还是一件大麻烦。所以,有没有相关的编译命令自动生成 / 验证 / 修复工具(的需求)?

救救孩子……

背景 / Background

  • 设备:Redmi Note 12 Turbo
  • 系统:MIUI Global 14.0.7,欧洲版(EEA,非 xiaomi.eu

目标 / The Challenge

在上述环境下用上公交卡

步骤 / Our Approach

  1. 安装 Magisk,获取 root 权限;
  2. 下载并安装 Xiaomi Wallet / com.mipay.walletMi Smart Cards / com.miui.tsmclient
  3. /data/adb/service.d 下创建一个脚本文件 set_se.sh,并赋予可执行权限。下面的命令可通过电脑端 adb shell 或手机端 Termux 执行:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ su
    # cd /data/adb/service.d
    # echo 'resetprop ro.vendor.se.type HCE,UICC,eSE' > set_se.sh
    # chmod +x set_se.sh
    # ls -l
    total 4
    -rwxr-xr-x 1 root root 41 2023-05-09 00:43 set_se.sh
    # cat set_se.sh
    resetprop ro.vendor.se.type HCE,UICC,eSE
  4. 重启,启动小米钱包的公交卡功能,按照提示打开 NFC 设置,将默认付款应用设为内置安全模块

每步都干了啥?

  • 安装 Xiaomi Wallet——就是小米钱包啦。
  • 安装 Mi Smart Cards——为了让钱包的界面顶部出现交通卡那一栏图标。
  • 创建 set_se.sh——这是一个 Magisk 启动脚本,通过修改系统属性默认付款应用中出现我们需要的选项。

Firefox的“刘海”真是越来越大了,垂直空间捉襟见肘.在userChrome.css中添加下面的代码,可以在非全屏模式下也自动隐藏地址栏和书签栏(当光标移上去、按下Alt-D或处于新标签页时会重新出现),并让选项卡的高度缩小一点:

1
2
3
4
5
6
7
8
9
10
11
12
:root[sessionrestored]
#navigator-toolbox:not([inFullscreen="true"]):not(:focus-within):not(:hover) #nav-bar,
:root[sessionrestored]
#navigator-toolbox:not([inFullscreen="true"]):not(:focus-within):not(:hover) #PersonalToolbar {
height: 0 !important;
min-height: 0 !important;
opacity: 0 !important;
}

#navigator-toolbox:not([inFullscreen="true"]) {
--tab-min-height: 28px !important;
}

效果如图:

firefox.gif

偶然看到这个问题:为什么互为逆否的命题同真假? - 知乎,尝试证明如下:

1
2
3
4
5
6
7
Definition excluded_middle_statement := forall (P : Prop), P \/ ~P.
Definition double_negation_statement := forall (P : Prop), ~~P -> P.
Definition contraposition_2 := forall P Q, (~Q -> ~P) -> (P -> Q).

Theorem excluded_middle_iff_contraposition_2 :
excluded_middle_statement <-> contraposition_2.
Proof with try tauto. unfold excluded_middle_statement, contraposition_2. split; intros. destruct (H Q)... assert double_negation_statement. { intros Q. specialize (H True Q)... } apply H0... Qed.

虽然过程很简单但还是记录一下.

安装crossdev

1
# emerge -av sys-devel/crossdev

创建 overlay

然后先来建立一个 overlay,让crossdev自动把生成的 ebuild 放在里面[1]

1
2
3
4
# mkdir -p /var/db/repos/cross-riscv64-linux-gnu/{profiles,metadata}
# echo 'cross-riscv64-linux-gnu' > /var/db/repos/cross-riscv64-linux-gnu/profiles/repo_name
# echo 'masters = gentoo' > /var/db/repos/cross-riscv64-linux-gnu/metadata/layout.conf
# chown -R portage:portage /var/db/repos/cross-riscv64-linux-gnu

这里的riscv64-linux-gnuTarget Triple,后面还会用到.

注意如果用 Git 方式同步gentoo源(或者其他Manifest文件不含 ebuild 的校验码的方式),需要在layout.conf里添加一行:

1
echo 'thin-manifests = true' >> /var/db/repos/cross-riscv64-linux-gnu/metadata/layout.conf

最后启用这个 overlay.在/etc/portage/repos.conf/crossdev.conf中写入:

1
2
3
4
5
[cross-riscv64-linux-gnu]
location = /var/db/repos/cross-riscv64-linux-gnu
priority = 10
masters = gentoo
auto-sync = no

构建工具链

执行以下命令安装工具链:

1
# crossdev --stable --ex-gdb -t riscv64-linux-gnu -oS gentoo

此处各个参数的含义如下:

  • --stable表示安装稳定 keyword 的版本,不带--stable会安装最新版(~*);
  • --ex-gdb表示除了 GCC 等基本工具外还要安装 GDB;
  • -t指定 target triple,可以用crossdev -t help列出合法的值;
  • -oS表示从哪些 overlay 里找 ebuild,这里指定gentoo是因为crossdev不知为什么默认会使用gentoo-zh里的gdb

可能会因为/etc/portage/package.mask等配置文件存在但不是目录而失败,这时只要把它们转换成目录即可(创建同名目录,把原来的配置文件变成目录下的一个普通文件).

执行完该命令后会得到/usr/riscv64-linux-gnu这个目录.

可以试着编译一个简单的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cd /tmp
$ cat > hello.c
#include <stdio.h>

int main()
{
printf("hello, world\n");
}
$ riscv64-linux-gnu-gcc -o hello hello.c
$ file hello
hello: ELF 64-bit LSB pie executable, UCB RISC-V, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1, for GNU/Linux 4.15.0, not stripped
$ ./hello
bash: ./hello: cannot execute binary file: Exec format error

安装 QEMU

可以用 QEMU 来直接运行 riscv64 程序.首先要启用 QEMU 模拟 riscv64 架构的功能,比如在/etc/portage/package.use中添加

1
app-emulation/qemu qemu_softmmu_targets_riscv64 qemu_user_targets_riscv64

然后重新安装 QEMU:

1
# emerge -av qemu

现在可以试着运行上面编译的hello程序:

1
2
$ qemu-riscv64 -L /usr/riscv64-linux-gnu /tmp/hello
hello, world

  1. https://wiki.gentoo.org/wiki/Custom_ebuild_repository#Crossdev ↩︎

某门课程需要用到Quartus和ModelSim.虽然官方友好地提供了Linux版本,但让旧版的软件在最新的系统上跑起来也需要一番工夫.
阅读全文 »

引子

听说不少人一放假就喜欢装黑苹果,然而手上并没有Hackintosh-friendly的机器的我,只能用重装Gentoo的方式来祭奠一下逝去的青春啦(悲)。

这里随便记录一些安装和配置过程中的琐事,便于日后查询。

持续更新中。

阅读全文 »

手贱把Nextcloud从17.0.2更新到了18.0.0 (Beta Channel),结果occ upgrade的时候fail在下面这个错误上:

1
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'entity' in 'where clause'

幸而手动加上这一列以后就能正常完成更新了,顺带复习了一下SQL(逃

1
ALTER TABLE "oc_flow_operation" ADD entity varchar(256) COLLATE utf8mb4_bin NOT NULL DEFAULT '';

P. S. GitHub上亦有此issue,看起来受影响的人还不少……见Issue while upgrading to 18 Beta1 (SQL, related to Flow) · Issue #18265 · nextcloud/server.

涉事笔记本系一台神舟Z7M-CT5NA,发行版为Arch Linux,或许也适用于其他症状相同的机器和/或发行版.

问题

详细症状如下:

  • 能成功休眠;
  • 重启后加载完休眠映像后迅速黑屏,这时reisub无效且硬盘灯不亮;
  • 日志结束在PM: hibernation entry处.

解决方案

一番周折后开始考虑内核问题. 在archlinux_pkg_linux directory listing可以找到linux这个包的所有历史版本. 用二分法可以锁定到是4.16.7版本的某些更新内容导致了唤醒失败,于是找到了这个页面:199747 – resume from hibernate results in kernel panic (bisected).

(报告人表示重启后Caps Lock狂闪,然而神舟的这台笔记本并没有键盘指示灯……)

网页上给出的一种解决方案是把intel_lpss_pci这个模块放在initramfs里面. 在Arch Linux上编辑/etc/mkinitcpio.conf

1
MODULES=(... intel_lpss_pci ...)

然后重新生成initramfs:

1
# mkinitcpio -P

重启后应该就可以正常唤醒了.

Troubleshooting

触摸板

执行上述步骤可以正常唤醒,然而唤醒以后触摸板会死掉.

按照网页上的提示,需要在唤醒后重新加载intel_lpss_pci这个模块。创建/etc/systemd/system/touchpad-after-resume.service并添加以下内容:

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Reload touchpad
After=sleep.target suspend.target hibernate.target hybrid-sleep.target

[Service]
Type=oneshot
ExecStart=sh -c 'modprobe -r intel_lpss_pci && modprobe intel_lpss_pci'

[Install]
WantedBy=sleep.target suspend.target hibernate.target hybrid-sleep.target

然后启用这个unit.

1
# systemctl enable touchpad-after-resume

现在触摸板可以在唤醒后正常工作了,但是所有设置(滚动、单击、移动速度……)全都会被重置,只有在系统设置里把触摸板设置重新应用一次解决方案在下面:

Update 2020-01-19: 今天发现写在/etc/X11/xorg.conf.d/30-touchpad.conf里的触摸板设置会在唤醒后(实际上应该是X重新检测到触摸板的时候)自动应用,所以只需要把相关设置放在这个文件里即可. 步骤如下:

  1. 执行xinput,找到写有Touchpad之类字眼的那一行,记住id=后的数字;
  2. xinput list-props <id>列出触摸板设置,重点关注与Default不一样的值;
  3. 创建/etc/X11/xorg.conf.d/30-touchpad.conf,写入如下内容:
1
2
3
4
5
Section "InputClass"
Identifier "Touchpad"
Driver "libinput"
...
EndSection

然后参照libinput(4)把触摸板设置翻译成Xorg配置文件的格式,填在…的地方就可以啦.