问题描述:
今天遇到一个中文日志输出到终端显示不出来的问题。
用户要升级操作系统,由redhat7.9
升级到redhat8.6
,x86_64
的环境。升级完后,交易服务端程序启动过程中,预期是会在终端输出一些标准输出或标准错误的日志信息,用于提示服务端程序启动过程中的状态,日志信息中包含中文字符,程序中是通过类似于fprintf(stdout/stderr, "hh:mm:ss.ffffff <ErrNo> <LogLevel> <PID> <TID> %ls<日志正文,可能含有中文字段,宽字符> (<FuncName> <FILE:LINE> ...)\n", lpLogInfo)
的方式输出的,中文编码是宽字符,使用%ls
输出,预期的输出效果如图1所示,但实际输出效果如图2所示。
图1
图2
之前对于中文字符的编码使用这块,没怎么研究过,因此借此机会,定位和记录下排查过程
原因分析:
1,启动参数检查
- 我们的启动脚本中,会有对
LANG
环境变量的设置,如:export LANG="zh_CN.UTF8"
,问题环境上,该设置也是有的。
2,strace跟踪
- 既然是屏幕输出,那就涉及系统调用,使用strace命令捕捉一下系统调用,看下传给内核的入参是否有问题:
strace -o strace_out.txt -T -tt -e trace=all -f -v -s 1024 TradeServer start ......
- 过滤
write(1,
或write(2,
(忽略时间点的差异,与图2不是同一次启动所截的图) - 如图3所示,系统调用
write(1,
时,传入的原串就已不包含中文的宽字符,说明在构造原串时,就已经出现了问题。
图3
用一个简单的demo,模拟问题场景,好在问题场景很容易复现
#include <wchar.h>
#include <stdio.h>
int main()
{
auto str = L"Hello, 你好";
auto len = wcslen(str);
fprintf(stdout, "Length of the string is %u, [%ls]\n", len, str);
return 0;
}
[root@null trade]# g++ test.cpp
[root@null trade]# export LANG=zh_CN.UTF8
[root@null trade]# ./a.out
Length of the string is 9, [[root@null trade]#
- 的确,输出到了中文处,文本就被截断了,为什么会这样?
3,locale
- 首先要搞清楚
locale
环境,之前遇到过这个配置命令,我起初咋一看还以为是locate
这个单词…
“Locale” 这个术语来源于拉丁语 “locus”,意思是 “地点” 或 “位置”。在计算机科学和编程中的具体上下文中,“locale” 用来表示特定的地域、语言和文化环境,它影响了程序在这些环境中的行为,特别是数据和信息的展示方式。
-
那它影响哪些方面呢?在程序中,这和以下环境变量有关
LC_COLLATE
:定义字符串的排序顺序,比如按字母顺序排列字符串的方法。这在各种语言中可能有所不同。LC_CTYPE
:定义字符分类和处理规则,比如字母字符、数字字符、空白字符等分类,以及字符转换函数的行为(如 tolower, toupper)。LC_MONETARY
:定义货币符号、格式和显示方式。这对于不同的国家和货币单位来说是不同的。LC_NUMERIC
:定义非货币数字的格式,包括小数点和千位分隔符的位置。这在各国可能有很大不同。LC_TIME
:定义日期和时间的格式,包括年月日的顺序、星期的显示方式、时间的表示等。LC_MESSAGES
:定义用户界面消息和响应的语言和格式,使应用程序能够使用本地语言向用户显示消息。LC_ADDRESS
/LC_IDENTIFICATION
/LC_MEASUREMENT
/LC_NAME
/LC_PAPER
/LC_TELEPHONE
:这些不常用的,咱就先不关心。LC_ALL
:用来覆盖所有其他 LC_* 设置的超级变量。如果设置了 LC_ALL,它将优先于所有其他 LC_* 及 LANG 设置。LANG
:用于设置系统的默认语言环境。除非明确设置了其他 LC_* 环境变量,否则它将应用到所有类别。
-
咱们重点关注
LANG
和LC_ALL
这两个环境变量LANG
:是在启动脚本中指定的export LANG=zh_CN.UTF-8
LC_ALL
:是框架在程序启动之初设置的setlocale(LC_ALL, "");
- 可以看到程序中设置的
LC_ALL
是一个空串,当传递的locale
是空字符串""
时,setlocale
将尝试从环境变量(如:LC_ALL
,LANG
,LC_CTYPE
等)中读取并设置locale
- 可以看到程序中设置的
- 所以预期程序中使用的
locale
应该是zh_CN.UTF-8
-
既然使用
setlocale
设置语言环境标识,那就看看是否有类似于getlocale
的接口,确定下set
有没有生效。因为原交易程序中没有对setlocale
的系统调用成功与否进行判断,因此这里选择gdb进行观察
(gdb) b setlocale
Breakpoint 1 at 0x7ffff6ae3520
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: ......
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, 0x00007ffff6ae3520 in setlocale () from /lib64/libc.so.6
(gdb) disassemble
Dump of assembler code for function setlocale:
=> 0x00007ffff6ae3520 <+0>: endbr64
0x00007ffff6ae3524 <+4>: push %r15
0x00007ffff6ae3526 <+6>: push %r14
0x00007ffff6ae3528 <+8>: push %r13
0x00007ffff6ae352a <+10>: push %r12
0x00007ffff6ae352c <+12>: push %rbp
0x00007ffff6ae352d <+13>: movslq %edi,%rbp
0x00007ffff6ae3530 <+16>: push %rbx
0x00007ffff6ae3531 <+17>: sub $0x118,%rsp
0x00007ffff6ae3538 <+24>: mov %fs:0x28,%rax
0x00007ffff6ae3541 <+33>: mov %rax,0x108(%rsp)
0x00007ffff6ae3549 <+41>: xor %eax,%eax
0x00007ffff6ae354b <+43>: cmp $0xc,%rbp
0x00007ffff6ae354f <+47>: ja 0x7ffff6ae38a0 <setlocale+896>
0x00007ffff6ae3555 <+53>: mov %rsi,%rbx
0x00007ffff6ae3558 <+56>: test %rsi,%rsi
--Type <RET> for more, q to quit, c to continue without paging--q
Quit
(gdb) i registers edi
edi 0x6 6
(gdb) i registers rsi
rsi 0x4d72aa 5075626
(gdb) p (const char *)0x4d72aa
$2 = 0x4d72aa ""
(gdb) finish
Run till exit from #0 0x00007ffff6ae3520 in setlocale () from /lib64/libc.so.6
main (argc=6, argv=0x7fffffffe028) at xxx.cpp:174
174 xxx.cpp: No such file or directory.
(gdb) i registers eax
eax 0x0 0
(gdb) i registers rax
rax 0x0 0
edi
:是setlocale
的第一个形参,即LC_ALL
rsi
:是setlocale
的第一个形参,即""
eax
:是setlocale
的返回值,是一个空指针nullptr
- 直觉告诉我,这里肯定有问题!来看下接口返回值说明
RETURN VALUE
A successful call to setlocale() returns an opaque string that corresponds to the locale set. This string may be allocated in static storage. The string returned is such that a subsequent call with that string and its associated category will restore that part of the process’s locale. The return value is NULL if the request cannot be honored.
- 看下实际生效的语言环境标识。
(gdb) p (const char *)setlocale(6, 0)
$1 = 0x7ffff72639cd <_nl_C_name> "C"
(gdb) q
Quit anyway? (y or n) y
[root@null trade]# locale -a
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_COLLATE to default locale: No such file or directory
C
C.utf8
POSIX
en_AG
en_*
- 真相大白了:
redhat8.6
上,居然没有生成zh_CN.UTF-8
语言标识的配置文件。
解决方案:
剩下的就是生成配置文件的事儿了,过程中还遇到了个小插曲,安装完,生成完后,就能正常输出中文了。
[root@null trade]# localedef -i zh_CN -f UTF-8 zh_CN.UTF-8
[error] character map file `UTF-8' not found: No such file or directory
[error] default character map file `ANSI_X3.4-1968' not found: No such file or directory
[root@null trade]# dnf install glibc-locale-source
Last metadata expiration check: 2:37:35 ago on Thu 07 Nov 2024 02:28:19 PM CST.
Dependencies resolved.
======================================================================================================================================================
Package Architecture Version Repository Size
======================================================================================================================================================
Installing:
glibc-locale-source x86_64 2.28-189.1.el8 base 4.2 M
Transaction Summary
======================================================================================================================================================
Install 1 Package
Total size: 4.2 M
Installed size: 15 M
Is this ok [y/N]: y
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : glibc-locale-source-2.28-189.1.el8.x86_64 1/1
Verifying : glibc-locale-source-2.28-189.1.el8.x86_64 1/1
Installed products updated.
Installed:
glibc-locale-source-2.28-189.1.el8.x86_64
Complete!
[root@null trade]# localedef -i zh_CN -f UTF-8 zh_CN.UTF-8
[root@null trade]# locale -a
C
C.utf8
en_AG
en_xxx
POSIX
zh_CN.utf8
[root@null trade]# ./a.out
Length of the string is 9, [Hello, 你好]
标签:中文,zh,LC,setlocale,locale,gdb,Linux,日志,root
From: https://blog.csdn.net/qq_39827740/article/details/143602385