1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Ebitengine Authors
3
4//go:build darwin || (linux && (386 || amd64 || arm || arm64 || loong64 || ppc64le || riscv64 || s390x))
5
6package purego_test
7
8import (
9 "fmt"
10 "os"
11 "path/filepath"
12 "runtime"
13 "testing"
14 "unsafe"
15
16 "github.com/bnema/purego"
17)
18
19// TestCallGoFromSharedLib is a test that checks for stack corruption on arm64
20// when C calls Go code from a non-Go thread in a dynamically loaded share library.
21func TestCallGoFromSharedLib(t *testing.T) {
22 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")
23 t.Logf("Build %v", libFileName)
24
25 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {
26 t.Fatal(err)
27 }
28 defer os.Remove(libFileName)
29
30 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
31 if err != nil {
32 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
33 }
34
35 var callCallback func(p uintptr, s string) int
36 purego.RegisterLibFunc(&callCallback, lib, "callCallback")
37
38 goFunc := func(cstr *byte, n int) int {
39 s := string(unsafe.Slice(cstr, n))
40 t.Logf("FROM Go: %s\n", s)
41 return 1
42 }
43
44 const want = 10101
45 cb := purego.NewCallback(goFunc)
46 for i := range 10 {
47 got := callCallback(cb, "a test string")
48 if got != want {
49 t.Fatalf("%d: callCallback() got %v want %v", i, got, want)
50 }
51 }
52}
53
54// TestCallbackStringConversion tests that C char* arguments are correctly
55// converted to Go strings when the callback signature uses string type.
56func TestCallbackStringConversion(t *testing.T) {
57 libFileName := filepath.Join(t.TempDir(), "libcbtest.so")
58 t.Logf("Build %v", libFileName)
59
60 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {
61 t.Fatal(err)
62 }
63 defer os.Remove(libFileName)
64
65 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
66 if err != nil {
67 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
68 }
69
70 var callCallback func(p uintptr, s string) int
71 purego.RegisterLibFunc(&callCallback, lib, "callCallback")
72
73 const expected = "a test string"
74 var got string
75 goFunc := func(s string, n int) int {
76 got = s
77 if len(s) != n {
78 t.Errorf("string length mismatch: got %d, want %d", len(s), n)
79 }
80 return 1
81 }
82
83 cb := purego.NewCallback(goFunc)
84 callCallback(cb, expected)
85
86 if got != expected {
87 t.Errorf("string callback got %q, want %q", got, expected)
88 }
89}
90
91func TestNewCallbackFloat64(t *testing.T) {
92 // This tests the maximum number of arguments a function to NewCallback can take
93 const (
94 expectCbTotal = -3
95 expectedCbTotalF = float64(36)
96 )
97 var cbTotal int
98 var cbTotalF float64
99 imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int,
100 f1, f2, f3, f4, f5, f6, f7, f8 float64,
101 ) {
102 cbTotal = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9
103 cbTotalF = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8
104 })
105 var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9 int,
106 f1, f2, f3, f4, f5, f6, f7, f8 float64)
107 purego.RegisterFunc(&fn, imp)
108 fn(1, 2, -3, 4, -5, 6, -7, 8, -9,
109 1, 2, 3, 4, 5, 6, 7, 8)
110
111 if cbTotal != expectCbTotal {
112 t.Errorf("cbTotal not correct got %d but wanted %d", cbTotal, expectCbTotal)
113 }
114 if cbTotalF != expectedCbTotalF {
115 t.Errorf("cbTotalF not correct got %f but wanted %f", cbTotalF, expectedCbTotalF)
116 }
117}
118
119func TestNewCallbackFloat64AndIntMix(t *testing.T) {
120 // This tests interleaving float and integer arguments to NewCallback
121 const (
122 expectCbTotal = 54.75
123 )
124 var cbTotal float64
125 imp := purego.NewCallback(func(a1, a2 float64, a3, a4, a5 int, a6, a7, a8 float64, a9 int) {
126 cbTotal = a1 + a2 + float64(a3) + float64(a4) + float64(a5) + a6 + a7 + a8 + float64(a9)
127 })
128 var fn func(a1, a2 float64, a3, a4, a5 int, a6, a7, a8 float64, a9 int)
129 purego.RegisterFunc(&fn, imp)
130 fn(1.25, 3.25, 4, 5, 6, 7.5, 8.25, 9.5, 10)
131
132 if cbTotal != expectCbTotal {
133 t.Errorf("cbTotal not correct got %f but wanted %f", cbTotal, expectCbTotal)
134 }
135}
136
137func TestNewCallbackFloat32(t *testing.T) {
138 // This tests the maximum number of float32 arguments a function to NewCallback can take
139 const (
140 expectCbTotal = 6
141 expectedCbTotalF = float32(45)
142 )
143 var cbTotal int
144 var cbTotalF float32
145 imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8 int,
146 f1, f2, f3, f4, f5, f6, f7, f8, f9 float32,
147 ) {
148 cbTotal = a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8
149 cbTotalF = f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9
150 })
151 var fn func(a1, a2, a3, a4, a5, a6, a7, a8 int,
152 f1, f2, f3, f4, f5, f6, f7, f8, f9 float32)
153 purego.RegisterFunc(&fn, imp)
154 fn(1, 2, -3, 4, -5, 6, -7, 8,
155 1, 2, 3, 4, 5, 6, 7, 8, 9)
156
157 if cbTotal != expectCbTotal {
158 t.Errorf("cbTotal not correct got %d but wanted %d", cbTotal, expectCbTotal)
159 }
160 if cbTotalF != expectedCbTotalF {
161 t.Errorf("cbTotalF not correct got %f but wanted %f", cbTotalF, expectedCbTotalF)
162 }
163}
164
165func TestNewCallbackFloat32AndFloat64(t *testing.T) {
166 if runtime.GOARCH == "s390x" {
167 // S390X has only 4 float registers (F0,F2,F4,F6), so 15 floats requires
168 // 11 stack slots, but only 10 are available (maxArgs - numIntRegs = 15 - 5)
169 t.Skip("Test requires too many stack arguments for s390x")
170 }
171 // This tests that calling a function with a mix of float32 and float64 arguments works
172 const (
173 expectedCbTotalF32 = float32(72)
174 expectedCbTotalF64 = float64(48)
175 )
176 var cbTotalF32 float32
177 var cbTotalF64 float64
178 imp := purego.NewCallback(func(f1, f2, f3 float32, f4, f5, f6 float64, f7, f8, f9 float32, f10, f11, f12 float64, f13, f14, f15 float32) {
179 cbTotalF32 = f1 + f2 + f3 + f7 + f8 + f9 + f13 + f14 + f15
180 cbTotalF64 = f4 + f5 + f6 + f10 + f11 + f12
181 })
182 var fn func(f1, f2, f3 float32, f4, f5, f6 float64, f7, f8, f9 float32, f10, f11, f12 float64, f13, f14, f15 float32)
183 purego.RegisterFunc(&fn, imp)
184 fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
185
186 if cbTotalF32 != expectedCbTotalF32 {
187 t.Errorf("cbTotalF32 not correct got %f but wanted %f", cbTotalF32, expectedCbTotalF32)
188 }
189 if cbTotalF64 != expectedCbTotalF64 {
190 t.Errorf("cbTotalF64 not correct got %f but wanted %f", cbTotalF64, expectedCbTotalF64)
191 }
192}
193
194func TestNewCallbackInt64Result(t *testing.T) {
195 // A 64-bit result needs two words: EDX:EAX on 386 and R1:R0 on arm.
196 // The trampoline must forward both halves of the value.
197 const mask = int64(0x0123456789abcdef)
198 imp := purego.NewCallback(func(v int64) int64 { return v ^ mask })
199 var fn func(v int64) int64
200 purego.RegisterFunc(&fn, imp)
201 for _, v := range []int64{
202 0,
203 -1,
204 1 << 32,
205 -0x7edcba9876543210,
206 } {
207 if got, want := fn(v), v^mask; got != want {
208 t.Errorf("callback(%#x) = %#x, want %#x", uint64(v), uint64(got), uint64(want))
209 }
210 }
211}
212
213func TestNewCallbackUint64Result(t *testing.T) {
214 // The upper half of a uint64 result must not be dropped by the trampoline.
215 imp := purego.NewCallback(func(lo, hi uint32) uint64 { return uint64(hi)<<32 | uint64(lo) })
216 var fn func(lo, hi uint32) uint64
217 purego.RegisterFunc(&fn, imp)
218 const want = uint64(0x0123456789abcdef)
219 if got := fn(0x89abcdef, 0x01234567); got != want {
220 t.Errorf("callback() = %#x, want %#x", got, want)
221 }
222}
223
224func TestNewCallbackInt64ResultWithStackArgs(t *testing.T) {
225 // On 386 all the arguments are passed on the stack, so this also checks
226 // that the copied arguments do not overlap the result of the callback.
227 // The number of arguments is kept small enough for ppc64le, which only
228 // has maxArgs(15) slots in total, 8 of them in integer registers.
229 const wantSum = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12
230 imp := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 int) int64 {
231 return int64(a1+a2+a3+a4+a5+a6+a7+a8+a9+a10+a11+a12)<<32 | 0x0badf00d
232 })
233 var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 int) int64
234 purego.RegisterFunc(&fn, imp)
235 const want = int64(wantSum)<<32 | 0x0badf00d
236 if got := fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); got != want {
237 t.Errorf("callback() = %#x, want %#x", uint64(got), uint64(want))
238 }
239}
240
241func ExampleNewCallback() {
242 cb := purego.NewCallback(func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int {
243 fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15)
244 return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15
245 })
246
247 var fn func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15 int) int
248 purego.RegisterFunc(&fn, cb)
249
250 ret := fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)
251 fmt.Println(ret)
252
253 // Output: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
254 // 120
255}
256
257func ExampleNewCallback_cdecl() {
258 fn := func(_ purego.CDecl, a int) {
259 fmt.Println(a)
260 }
261 cb := purego.NewCallback(fn)
262 purego.SyscallN(cb, 83)
263
264 // Output: 83
265}
266
267func TestCallbackInt32Packing(t *testing.T) {
268 if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
269 t.Skip("callback tight packing only applies to darwin/arm64")
270 }
271
272 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
273 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
274 t.Fatal(err)
275 }
276 defer os.Remove(libFileName)
277
278 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
279 if err != nil {
280 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
281 }
282
283 var callCallback12Int32 func(cb uintptr) int32
284 purego.RegisterLibFunc(&callCallback12Int32, lib, "callCallback12Int32")
285
286 // Go callback that sums the 12 int32 arguments (prime numbers: 2,3,5,7,11,13,17,19,23,29,31,37)
287 goFunc := func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12 int32) int32 {
288 return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12
289 }
290
291 cb := purego.NewCallback(goFunc)
292 got := callCallback12Int32(cb)
293 want := int32(2 + 3 + 5 + 7 + 11 + 13 + 17 + 19 + 23 + 29 + 31 + 37) // 197
294 if got != want {
295 t.Errorf("callCallback12Int32() = %d, want %d", got, want)
296 }
297}
298
299func TestCallbackMixedStackPacking(t *testing.T) {
300 if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
301 t.Skip("callback tight packing only applies to darwin/arm64")
302 }
303
304 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
305 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
306 t.Fatal(err)
307 }
308 defer os.Remove(libFileName)
309
310 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
311 if err != nil {
312 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
313 }
314
315 var callCallbackMixedStack func(cb uintptr) int64
316 purego.RegisterLibFunc(&callCallbackMixedStack, lib, "callCallbackMixedStack")
317
318 // Go callback: 8 int64s in regs, then int32(100), int64(200), int32(300) on stack
319 goFunc := func(a1, a2, a3, a4, a5, a6, a7, a8 int64, s1 int32, s2 int64, s3 int32) int64 {
320 // Return sum to verify all args received correctly
321 return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + int64(s1) + s2 + int64(s3)
322 }
323
324 cb := purego.NewCallback(goFunc)
325 got := callCallbackMixedStack(cb)
326 want := int64(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 100 + 200 + 300) // 636
327 if got != want {
328 t.Errorf("callCallbackMixedStack() = %d, want %d", got, want)
329 }
330}
331
332func TestCallbackSmallTypesPacking(t *testing.T) {
333 if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
334 t.Skip("callback tight packing only applies to darwin/arm64")
335 }
336
337 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
338 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
339 t.Fatal(err)
340 }
341 defer os.Remove(libFileName)
342
343 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
344 if err != nil {
345 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
346 }
347
348 var callCallbackSmallTypes func(cb uintptr) int64
349 purego.RegisterLibFunc(&callCallbackSmallTypes, lib, "callCallbackSmallTypes")
350
351 // Values: 1,2,3,4,5,6,7,8 (regs), true, -42, 200, -1000, 50000, 123456 (stack)
352 var gotBool bool
353 var gotI8 int8
354 var gotU8 uint8
355 var gotI16 int16
356 var gotU16 uint16
357 var gotI32 int32
358
359 goFunc := func(a1, a2, a3, a4, a5, a6, a7, a8 int64,
360 b bool, i8 int8, u8 uint8, i16 int16, u16 uint16, i32 int32) int64 {
361 gotBool = b
362 gotI8 = i8
363 gotU8 = u8
364 gotI16 = i16
365 gotU16 = u16
366 gotI32 = i32
367 return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8
368 }
369
370 cb := purego.NewCallback(goFunc)
371 got := callCallbackSmallTypes(cb)
372
373 // Check register args sum
374 want := int64(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8) // 36
375 if got != want {
376 t.Errorf("register args sum = %d, want %d", got, want)
377 }
378
379 // Check stack args
380 if gotBool != true {
381 t.Errorf("bool = %v, want true", gotBool)
382 }
383 if gotI8 != -42 {
384 t.Errorf("int8 = %d, want -42", gotI8)
385 }
386 if gotU8 != 200 {
387 t.Errorf("uint8 = %d, want 200", gotU8)
388 }
389 if gotI16 != -1000 {
390 t.Errorf("int16 = %d, want -1000", gotI16)
391 }
392 if gotU16 != 50000 {
393 t.Errorf("uint16 = %d, want 50000", gotU16)
394 }
395 if gotI32 != 123456 {
396 t.Errorf("int32 = %d, want 123456", gotI32)
397 }
398}
399
400func TestCallback10Int32Packing(t *testing.T) {
401 if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
402 t.Skip("callback tight packing only applies to darwin/arm64")
403 }
404
405 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
406 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
407 t.Fatal(err)
408 }
409 defer os.Remove(libFileName)
410
411 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
412 if err != nil {
413 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
414 }
415
416 var callCallback10Int32 func(cb uintptr) int32
417 purego.RegisterLibFunc(&callCallback10Int32, lib, "callCallback10Int32")
418
419 goFunc := func(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 int32) int32 {
420 return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10
421 }
422
423 cb := purego.NewCallback(goFunc)
424 got := callCallback10Int32(cb)
425 want := int32(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10) // 55
426 if got != want {
427 t.Errorf("callCallback10Int32() = %d, want %d", got, want)
428 }
429}
430
431func TestCallbackFloat64StackPacking(t *testing.T) {
432 if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
433 t.Skip("callback tight packing only applies to darwin/arm64")
434 }
435
436 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
437 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
438 t.Fatal(err)
439 }
440 defer os.Remove(libFileName)
441
442 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
443 if err != nil {
444 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
445 }
446
447 var callCallback10Float64 func(cb uintptr) int64
448 purego.RegisterLibFunc(&callCallback10Float64, lib, "callCallback10Float64")
449
450 // 10 float64s: 8 in registers, 2 on stack
451 // Return int64 since callbacks don't support float returns
452 goFunc := func(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10 float64) int64 {
453 sum := f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10
454 return int64(sum * 10) // 60.0 * 10 = 600
455 }
456
457 cb := purego.NewCallback(goFunc)
458 got := callCallback10Float64(cb)
459 want := int64(600)
460 if got != want {
461 t.Errorf("callCallback10Float64() = %d, want %d", got, want)
462 }
463}
464
465func TestCallbackFloat32StackPacking(t *testing.T) {
466 if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
467 t.Skip("callback tight packing only applies to darwin/arm64")
468 }
469
470 libFileName := filepath.Join(t.TempDir(), "libcbtest_packing.so")
471 if err := buildSharedLib(t, "CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_packing_test.c")); err != nil {
472 t.Fatal(err)
473 }
474 defer os.Remove(libFileName)
475
476 lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
477 if err != nil {
478 t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
479 }
480
481 var callCallback12Float32 func(cb uintptr) int64
482 purego.RegisterLibFunc(&callCallback12Float32, lib, "callCallback12Float32")
483
484 // 12 float32s: 8 in registers, 4 on stack
485 // Return int64 since callbacks don't support float returns
486 goFunc := func(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12 float32) int64 {
487 sum := f1 + f2 + f3 + f4 + f5 + f6 + f7 + f8 + f9 + f10 + f11 + f12
488 return int64(sum) // 78
489 }
490
491 cb := purego.NewCallback(goFunc)
492 got := callCallback12Float32(cb)
493 want := int64(78)
494 if got != want {
495 t.Errorf("callCallback12Float32() = %d, want %d", got, want)
496 }
497}
498
499func TestRegisterFuncExpandFinalAnyArgument(t *testing.T) {
500 // The last argument is expanded into the C arguments held in it,
501 // whether it is declared as ...any or as []any (#506).
502 cb := purego.NewCallback(func(a, b, c uintptr) uintptr {
503 return a*100 + b*10 + c
504 })
505 const want = uintptr(123)
506 t.Run("Variadic", func(t *testing.T) {
507 var fn func(a uintptr, args ...any) uintptr
508 purego.RegisterFunc(&fn, cb)
509 if got := fn(1, uintptr(2), uintptr(3)); got != want {
510 t.Errorf("func(uintptr, ...any) = %d, want %d", got, want)
511 }
512 })
513 t.Run("Slice", func(t *testing.T) {
514 var fn func(a uintptr, args []any) uintptr
515 purego.RegisterFunc(&fn, cb)
516 if got := fn(1, []any{uintptr(2), uintptr(3)}); got != want {
517 t.Errorf("func(uintptr, []any) = %d, want %d", got, want)
518 }
519 })
520}
521
522func TestRegisterFuncKeepNonFinalAnyArgument(t *testing.T) {
523 // A []any that isn't the last argument is a regular argument:
524 // it is passed as one value and must not panic (#506).
525 var first any
526 var second uintptr
527 cb := purego.NewCallback(func(args *any, b uintptr) uintptr {
528 first = *args
529 second = b
530 return 0
531 })
532 var fn func(args []any, b uintptr) uintptr
533 purego.RegisterFunc(&fn, cb)
534 s := []any{uintptr(2), uintptr(3)}
535 fn(s, 7)
536 runtime.KeepAlive(s)
537 if want := any(uintptr(2)); first != want {
538 t.Errorf("the first C argument = %v, want %v", first, want)
539 }
540 if second != 7 {
541 t.Errorf("the second C argument = %d, want 7", second)
542 }
543}