回答編集履歴
3
追加調査事項を追記
test
CHANGED
@@ -168,3 +168,122 @@
|
|
168
168
|
あとは`fputwc()`から`_write()`が呼ばれることを実証できれば因果関係が確認できます。そのために`test_fputwc.c`を生成、ビルドして、`gdb`というデバッガで実行しています。このデバッガはコマンドで動くタイプなので、コマンドをhere documentで直に記述しています。
|
169
169
|
|
170
170
|
やっていることは、一度実行してDLLを読み込ませ、`_write()`にブレークポイントをしかけて実行し、止まったらコールスタックを表示し、引数を見るためにレジスタを表示し(第1引数がrcx, 第2引数がrdx, 第3引数がr8)、第2引数はポインタなのでメモリのダンプをしているだけです。あとはブレークポイントを解除して残りの処理を実行させて終わりです。
|
171
|
+
|
172
|
+
---
|
173
|
+
|
174
|
+
#### 追記
|
175
|
+
若干調査してわかったことがあるので追記しておきます。
|
176
|
+
```bash
|
177
|
+
locales=("C" ".932" ".UTF-8" ".65001" ".20932")
|
178
|
+
first=("\\x96" "\\x96" "\\xe6" "\\xe6" "\\xcb")
|
179
|
+
second=("\\x7b" "\\x7b" "\\x9c" "\\x9c" "\\xdc")
|
180
|
+
third=("" "" "\\xac" "\\xac" "")
|
181
|
+
for i in `seq 0 4`;do
|
182
|
+
loc=${locales[$i]}
|
183
|
+
fbase="test_write_locale_${loc}"
|
184
|
+
cat >${fbase}.c <<EOF
|
185
|
+
#include <stdio.h>
|
186
|
+
#include <locale.h>
|
187
|
+
#include <io.h>
|
188
|
+
#include <errno.h>
|
189
|
+
void test(char* s, size_t len) {
|
190
|
+
int r = _write(_fileno(stdout), s, len);
|
191
|
+
if (r != -1) {
|
192
|
+
printf("r=%d\\n", r);
|
193
|
+
} else {
|
194
|
+
perror("test_write:");
|
195
|
+
printf("r=%d errno=%d\\n", r, errno);
|
196
|
+
}
|
197
|
+
}
|
198
|
+
int main() {
|
199
|
+
if (setlocale(LC_CTYPE, "${loc}") == NULL) {
|
200
|
+
fprintf(stderr, "setlocale fail!\\n");
|
201
|
+
return 1;
|
202
|
+
}
|
203
|
+
printf("[_write(1, \\"\\${first[$i]}\\", 1)]\\n");
|
204
|
+
test("${first[$i]}", 1); // 本の1バイト目だけ
|
205
|
+
EOF
|
206
|
+
if [ "${second[$i]}" != "" ]; then
|
207
|
+
cat >>${fbase}.c <<EOF
|
208
|
+
printf("[_write(1, \\"\\${first[$i]}\\${second[$i]}\\", 2)]\\n");
|
209
|
+
test("${first[$i]}${second[$i]}", 2); // 本の2バイト目まで
|
210
|
+
EOF
|
211
|
+
fi
|
212
|
+
if [ "${third[$i]}" != "" ]; then
|
213
|
+
cat >>${fbase}.c <<EOF
|
214
|
+
printf("[_write(1, \\"\\${first[$i]}\\${second[$i]}\\${third[$i]}\\", 3)]\\n");
|
215
|
+
test("${first[$i]}${second[$i]}${third[$i]}", 3); // 本の3バイト目まで
|
216
|
+
EOF
|
217
|
+
fi
|
218
|
+
cat >>${fbase}.c <<EOF
|
219
|
+
return 0;
|
220
|
+
}
|
221
|
+
EOF
|
222
|
+
gcc -g -Wall ${fbase}.c -o ${fbase}
|
223
|
+
echo "=== ${fbase} ==="
|
224
|
+
./${fbase}
|
225
|
+
done
|
226
|
+
```
|
227
|
+
スクリプトを修正して他のロケールでどうなるか?UCRT64でどうなるか?を見てみました。
|
228
|
+
|
229
|
+
**MINGW64での結果**
|
230
|
+
```bash
|
231
|
+
=== test_write_locale_C ===
|
232
|
+
[_write(1, "\x96", 1)]
|
233
|
+
睦=1
|
234
|
+
[_write(1, "\x96\x7b", 2)]
|
235
|
+
本r=2
|
236
|
+
=== test_write_locale_.932 ===
|
237
|
+
[_write(1, "\x96", 1)]
|
238
|
+
test_write:: No space left on device
|
239
|
+
r=-1 errno=28
|
240
|
+
[_write(1, "\x96\x7b", 2)]
|
241
|
+
本r=2
|
242
|
+
=== test_write_locale_.UTF-8 ===
|
243
|
+
setlocale fail!
|
244
|
+
=== test_write_locale_.65001 ===
|
245
|
+
setlocale fail!
|
246
|
+
=== test_write_locale_.20932 ===
|
247
|
+
[_write(1, "\xcb", 1)]
|
248
|
+
test_write:: No space left on device
|
249
|
+
r=-1 errno=28
|
250
|
+
[_write(1, "\xcb\xdc", 2)]
|
251
|
+
本r=2
|
252
|
+
```
|
253
|
+
|
254
|
+
**UCRT64の結果**
|
255
|
+
```bash
|
256
|
+
=== test_write_locale_C ===
|
257
|
+
[_write(1, "\x96", 1)]
|
258
|
+
睦=1
|
259
|
+
[_write(1, "\x96\x7b", 2)]
|
260
|
+
本r=2
|
261
|
+
=== test_write_locale_.932 ===
|
262
|
+
[_write(1, "\x96", 1)]
|
263
|
+
睦=1
|
264
|
+
[_write(1, "\x96\x7b", 2)]
|
265
|
+
本r=2
|
266
|
+
=== test_write_locale_.UTF-8 ===
|
267
|
+
[_write(1, "\xe6", 1)]
|
268
|
+
[_write(1, "\xe6\x9c", 2)]
|
269
|
+
[_write(1, "\xe6\x9c\xac", 3)]
|
270
|
+
本r=3
|
271
|
+
=== test_write_locale_.65001 ===
|
272
|
+
[_write(1, "\xe6", 1)]
|
273
|
+
[_write(1, "\xe6\x9c", 2)]
|
274
|
+
[_write(1, "\xe6\x9c\xac", 3)]
|
275
|
+
本r=3
|
276
|
+
=== test_write_locale_.20932 ===
|
277
|
+
[_write(1, "\xcb", 1)]
|
278
|
+
?=1
|
279
|
+
[_write(1, "\xcb\xdc", 2)]
|
280
|
+
本r=2
|
281
|
+
```
|
282
|
+
|
283
|
+
**わかったこと**
|
284
|
+
|
285
|
+
- "C"ロケールでは問題が発生しないこと
|
286
|
+
- UCRT64では問題が発生しないこと
|
287
|
+
- MINGW64(MSVCRT)ではコードページ20932(EUC-JP)でも同様の問題が発生していること
|
288
|
+
- EUC-JPで正しく表示されていることからbreakpointで仕掛けてみたところ、**`_write()`呼出中**に`MultiByteToWideChar()`と`WideCharToMultiByte()`が呼ばれていた。つまり**MSVCRTでも端末のコードページに合わせてエンコーディングの変換をかけている**。
|
289
|
+
|
2
これ以上追えないので、回答としての体裁を整えて終わらせた。
test
CHANGED
@@ -1,10 +1,23 @@
|
|
1
|
-
### 回答
|
1
|
+
### 回答
|
2
2
|
|
3
|
-
|
3
|
+
現状では個人的に**不可能**と言わざるを得ません。
|
4
4
|
|
5
|
-
|
5
|
+
#### 原因
|
6
6
|
|
7
|
+
`setlocale()`で設定されたロケールに従いって端末出力する場合、`_write()`が2バイト文字の1バイト目のみを出力しようとすると、`errno`=`ENOSPC`なエラーとなるから。`fputwc()`は2バイト文字の1バイト目を出力するために`_write()`を使用する。
|
8
|
+
|
9
|
+
https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/write?view=msvc-140
|
10
|
+
|
11
|
+
を見る限り、`_write()`にそのような仕様はない。
|
12
|
+
|
13
|
+
#### 対策
|
14
|
+
|
15
|
+
VS2015がなく、この挙動がMINGW64によるビルドの影響なのか、MSVCRT.dllの実装のせいなのか、切り分けられないので、個人的には対処のしようがない。
|
16
|
+
→(個人的には)**不可能**
|
17
|
+
|
18
|
+
### 調査詳細
|
19
|
+
|
7
|
-
####
|
20
|
+
#### 調査用スクリプト
|
8
21
|
|
9
22
|
```bash
|
10
23
|
cat >test_write.c <<EOF
|
@@ -140,3 +153,18 @@
|
|
140
153
|
(gdb)
|
141
154
|
```
|
142
155
|
|
156
|
+
#### 簡単な解説
|
157
|
+
|
158
|
+
まずは`_write()`の挙動を確認するためのCファイル`test_write.c`をビルドして実行しています。実行結果の
|
159
|
+
|
160
|
+
> ```bash
|
161
|
+
> [_write(1, "\x96", 1)]
|
162
|
+
> test_write:: No space left on device
|
163
|
+
> r=-1 errno=28
|
164
|
+
> ```
|
165
|
+
|
166
|
+
という部分から、`_write()`でCP932の2バイト文字を1バイト分しか書き込もうとしなかったときに、`fputwc()`と同じ現象が発生してることが確認できます。
|
167
|
+
|
168
|
+
あとは`fputwc()`から`_write()`が呼ばれることを実証できれば因果関係が確認できます。そのために`test_fputwc.c`を生成、ビルドして、`gdb`というデバッガで実行しています。このデバッガはコマンドで動くタイプなので、コマンドをhere documentで直に記述しています。
|
169
|
+
|
170
|
+
やっていることは、一度実行してDLLを読み込ませ、`_write()`にブレークポイントをしかけて実行し、止まったらコールスタックを表示し、引数を見るためにレジスタを表示し(第1引数がrcx, 第2引数がrdx, 第3引数がr8)、第2引数はポインタなのでメモリのダンプをしているだけです。あとはブレークポイントを解除して残りの処理を実行させて終わりです。
|
1
実行ログも添えた
test
CHANGED
@@ -64,3 +64,79 @@
|
|
64
64
|
q
|
65
65
|
EOF
|
66
66
|
```
|
67
|
+
バージョンアップなどで将来挙動が変わる可能性もあるので、ログも控えておきます。
|
68
|
+
```bash
|
69
|
+
$ bash test_write.sh
|
70
|
+
[_write(1, "\x96", 1)]
|
71
|
+
test_write:: No space left on device
|
72
|
+
r=-1 errno=28
|
73
|
+
[_write(1, "\x96\x7b", 2)]
|
74
|
+
本r=2
|
75
|
+
GNU gdb (GDB) 13.1
|
76
|
+
Copyright (C) 2023 Free Software Foundation, Inc.
|
77
|
+
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
78
|
+
This is free software: you are free to change and redistribute it.
|
79
|
+
There is NO WARRANTY, to the extent permitted by law.
|
80
|
+
Type "show copying" and "show warranty" for details.
|
81
|
+
This GDB was configured as "x86_64-w64-mingw32".
|
82
|
+
Type "show configuration" for configuration details.
|
83
|
+
For bug reporting instructions, please see:
|
84
|
+
<https://www.gnu.org/software/gdb/bugs/>.
|
85
|
+
Find the GDB manual and other documentation resources online at:
|
86
|
+
<http://www.gnu.org/software/gdb/documentation/>.
|
87
|
+
|
88
|
+
For help, type "help".
|
89
|
+
Type "apropos word" to search for commands related to "word"...
|
90
|
+
Reading symbols from ./test_fputwc...
|
91
|
+
(gdb) Starting program: C:\msys64\home\user\mingw\test_fputwc.exe
|
92
|
+
[New Thread 16188.0x4b88]
|
93
|
+
test_fputwc: No space left on device
|
94
|
+
failed! errno=28 r=65535
|
95
|
+
[Thread 16188.0x4b88 exited with code 0]
|
96
|
+
[Inferior 1 (process 16188) exited normally]
|
97
|
+
(gdb) Breakpoint 1 at 0x7ffe8fbcfaeb
|
98
|
+
(gdb) Starting program: C:\msys64\home\user\mingw\test_fputwc.exe
|
99
|
+
[New Thread 17372.0x62bc]
|
100
|
+
|
101
|
+
Thread 1 hit Breakpoint 1, 0x00007ffe8fbcfaeb in msvcrt!_write ()
|
102
|
+
from C:\WINDOWS\System32\msvcrt.dll
|
103
|
+
(gdb) #0 0x00007ffe8fbcfaeb in msvcrt!_write () from C:\WINDOWS\System32\msvcrt.dll
|
104
|
+
#1 0x00007ffe8fbf719e in msvcrt!_flsbuf () from C:\WINDOWS\System32\msvcrt.dll
|
105
|
+
#2 0x00007ffe8fbfd521 in msvcrt!fputs () from C:\WINDOWS\System32\msvcrt.dll
|
106
|
+
#3 0x00007ffe8fbfd5ed in msvcrt!fputwc () from C:\WINDOWS\System32\msvcrt.dll
|
107
|
+
#4 0x00007ff74ccd14c4 in test (fp=0x7ffe8fc3fa30 <msvcrt!_iob+48>) at test_fputwc.c:6
|
108
|
+
#5 0x00007ff74ccd155e in main (argc=1, argv=0xd4870) at test_fputwc.c:16
|
109
|
+
(gdb) rax 0x40 64
|
110
|
+
rbx 0x7ffe8fc3fa30 140731310406192
|
111
|
+
rcx 0x1 1
|
112
|
+
rdx 0x5ffd90 6290832
|
113
|
+
rsi 0x1 1
|
114
|
+
rdi 0x0 0
|
115
|
+
rbp 0x1 0x1
|
116
|
+
rsp 0x5ffd00 0x5ffd00
|
117
|
+
r8 0x1 1
|
118
|
+
r9 0x5ffdb6 6290870
|
119
|
+
r10 0x0 0
|
120
|
+
r11 0x5ffd40 6290752
|
121
|
+
r12 0xd48b0 870576
|
122
|
+
r13 0x0 0
|
123
|
+
r14 0x5ffdb4 6290868
|
124
|
+
r15 0x0 0
|
125
|
+
rip 0x7ffe8fbcfaeb 0x7ffe8fbcfaeb <msvcrt!_write+27>
|
126
|
+
eflags 0x206 [ PF IF ]
|
127
|
+
cs 0x33 51
|
128
|
+
ss 0x2b 43
|
129
|
+
ds 0x2b 43
|
130
|
+
es 0x2b 43
|
131
|
+
fs 0x53 83
|
132
|
+
gs 0x2b 43
|
133
|
+
(gdb) 0x5ffd90: 0x96 0xff 0xff 0xff 0x00 0x00 0x00 0x00
|
134
|
+
0x5ffd98: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
135
|
+
(gdb) (gdb) Continuing.
|
136
|
+
test_fputwc: No space left on device
|
137
|
+
failed! errno=28 r=65535
|
138
|
+
[Thread 17372.0x62bc exited with code 0]
|
139
|
+
[Inferior 1 (process 17372) exited normally]
|
140
|
+
(gdb)
|
141
|
+
```
|
142
|
+
|