Skip to content

Commit 187d61e

Browse files
authored
CA-409628: fix error-prone Debug.log_backtrace that always lost the important part of the backtrace (#6430)
Before/after can be seen clearly in the newly added unit test: * previously line 8 was not printed anywhere, even though that is the actual place the exception is raised * the whole backtrace was printed on a single line, which can be quite annoying After: * line 8 is present * the correct backtrace printer is used that prints each stack frame on a separate line with a counter ```diff --git a/ocaml/libs/log/test/log_test.t b/ocaml/libs/log/test/log_test.t index f25ee70..fbdeebf 100644 --- a/ocaml/libs/log/test/log_test.t +++ b/ocaml/libs/log/test/log_test.t @@ -1,4 +1,9 @@ $ ./log_test.exe | sed -re 's/[0-9]+T[0-9:.]+Z//' - [|debug||0 |main|log_test.ml] Raised at Xapi_stdext_pervasives__Pervasiveext.finally in file \"ocaml/libs/xapi-stdext/lib/xapi-stdext-pervasives/pervasiveext.ml\", line 39, characters 6-15\nCalled from Dune__exe__Log_test.(fun) in file \"ocaml/libs/log/test/log_test.ml\", line 15, characters 4-55\n + [|error||0 |main|backtrace] Raised Invalid_argument("index out of bounds") + [|error||0 |main|backtrace] 1/4 log_test.exe Raised at file ocaml/libs/log/test/log_test.ml, line 8 + [|error||0 |main|backtrace] 2/4 log_test.exe Called from file ocaml/libs/xapi-stdext/lib/xapi-stdext-pervasives/pervasiveext.ml, line 24 + [|error||0 |main|backtrace] 3/4 log_test.exe Called from file ocaml/libs/xapi-stdext/lib/xapi-stdext-pervasives/pervasiveext.ml, line 39 + [|error||0 |main|backtrace] 4/4 log_test.exe Called from file ocaml/libs/log/test/log_test.ml, line 15 + [|error||0 |main|backtrace] ```
2 parents 0ab649c + 8a2dba3 commit 187d61e

31 files changed

+172
-129
lines changed

ocaml/gencert/selfcert.ml

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ let generate_pub_priv_key length =
109109
let stdout, _stderr = call_openssl args in
110110
Ok stdout
111111
with e ->
112+
Backtrace.is_important e ;
112113
let msg = "generating RSA key failed" in
113114
D.error "selfcert.ml: %s" msg ;
114115
Debug.log_backtrace e (Backtrace.get e) ;

ocaml/libs/http-lib/dune

+92-98
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,103 @@
11
(library
2-
(name http_lib)
3-
(public_name http-lib)
4-
(modes best)
5-
(wrapped false)
6-
(modules (:standard \ http_svr http_proxy server_io http_test radix_tree_test test_client test_server))
7-
(preprocess (per_module ((pps ppx_deriving_rpc) Http)))
8-
(libraries
9-
astring
10-
base64
11-
fmt
12-
ipaddr
13-
mtime
14-
mtime.clock.os
15-
rpclib.core
16-
rpclib.json
17-
rpclib.xml
18-
safe_resources
19-
sha
20-
stunnel
21-
threads.posix
22-
uuid
23-
uri
24-
xapi-backtrace
25-
xapi-consts.xapi_version
26-
xapi-idl.updates
27-
xapi-log
28-
clock
29-
xapi-stdext-pervasives
30-
xapi-stdext-threads
31-
xapi-stdext-unix
32-
xml-light2
33-
)
34-
)
2+
(name http_lib)
3+
(public_name http-lib)
4+
(modes best)
5+
(wrapped false)
6+
(modules
7+
(:standard
8+
\
9+
http_svr
10+
http_proxy
11+
server_io
12+
http_test
13+
radix_tree_test
14+
test_client
15+
test_server))
16+
(preprocess
17+
(per_module
18+
((pps ppx_deriving_rpc)
19+
Http)))
20+
(libraries
21+
astring
22+
base64
23+
fmt
24+
ipaddr
25+
mtime
26+
mtime.clock.os
27+
rpclib.core
28+
rpclib.json
29+
rpclib.xml
30+
safe_resources
31+
sha
32+
stunnel
33+
threads.posix
34+
uuid
35+
uri
36+
xapi-backtrace
37+
xapi-consts.xapi_version
38+
xapi-idl.updates
39+
xapi-log
40+
clock
41+
xapi-stdext-pervasives
42+
xapi-stdext-threads
43+
xapi-stdext-unix
44+
xml-light2))
3545

3646
(library
37-
(name httpsvr)
38-
(wrapped false)
39-
(modes best)
40-
(modules http_svr http_proxy server_io)
41-
(libraries
42-
astring
43-
http_lib
44-
ipaddr
45-
polly
46-
tgroup
47-
threads.posix
48-
tracing
49-
tracing_propagator
50-
uri
51-
xapi-log
52-
xapi-stdext-pervasives
53-
xapi-stdext-threads
54-
xapi-stdext-unix
55-
)
56-
)
47+
(name httpsvr)
48+
(wrapped false)
49+
(modes best)
50+
(modules http_svr http_proxy server_io)
51+
(libraries
52+
astring
53+
http_lib
54+
ipaddr
55+
polly
56+
tgroup
57+
threads.posix
58+
tracing
59+
tracing_propagator
60+
uri
61+
xapi-backtrace
62+
xapi-log
63+
xapi-stdext-pervasives
64+
xapi-stdext-threads
65+
xapi-stdext-unix))
5766

5867
(tests
59-
(names http_test radix_tree_test)
60-
(package http-lib)
61-
(modes (best exe))
62-
(modules http_test radix_tree_test)
63-
(libraries
64-
alcotest
65-
66-
fmt
67-
http_lib
68-
)
69-
)
68+
(names http_test radix_tree_test)
69+
(package http-lib)
70+
(modes
71+
(best exe))
72+
(modules http_test radix_tree_test)
73+
(libraries alcotest fmt http_lib))
7074

7175
(executable
72-
(modes exe)
73-
(name test_client)
74-
(modules test_client)
75-
(libraries
76-
77-
http_lib
78-
safe-resources
79-
stunnel
80-
threads.posix
81-
xapi-backtrace
82-
xapi-log
83-
xapi-stdext-pervasives
84-
xapi-stdext-unix
85-
)
86-
)
76+
(modes exe)
77+
(name test_client)
78+
(modules test_client)
79+
(libraries
80+
http_lib
81+
safe-resources
82+
stunnel
83+
threads.posix
84+
xapi-backtrace
85+
xapi-log
86+
xapi-stdext-pervasives
87+
xapi-stdext-unix))
8788

8889
(executable
89-
(modes exe)
90-
(name test_server)
91-
(modules test_server)
92-
(libraries
93-
94-
http_lib
95-
httpsvr
96-
safe-resources
97-
threads.posix
98-
xapi-stdext-threads
99-
xapi-stdext-unix
100-
)
101-
)
90+
(modes exe)
91+
(name test_server)
92+
(modules test_server)
93+
(libraries
94+
http_lib
95+
httpsvr
96+
safe-resources
97+
threads.posix
98+
xapi-stdext-threads
99+
xapi-stdext-unix))
102100

103101
(cram
104-
(package xapi)
105-
(deps
106-
test_client.exe
107-
test_server.exe
108-
)
109-
)
102+
(package xapi)
103+
(deps test_client.exe test_server.exe))

ocaml/libs/http-lib/http_svr.ml

+4-3
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,9 @@ let response_request_header_fields_too_large s =
189189
response_error_html s "431" "Request Header Fields Too Large" [] body
190190

191191
let response_internal_error ?req ?extra exc s =
192+
Backtrace.is_important exc ;
192193
E.error "Responding with 500 Internal Error due to %s" (Printexc.to_string exc) ;
193-
E.log_backtrace () ;
194+
E.log_backtrace exc ;
194195
let version = Option.map get_return_version req in
195196
let extra =
196197
Option.fold ~none:""
@@ -473,6 +474,7 @@ let read_request ?proxy_seen ~read_timeout ~total_timeout ~max_length fd =
473474
in
474475
(Some r, proxy)
475476
with e ->
477+
Backtrace.is_important e ;
476478
D.warn "%s (%s)" (Printexc.to_string e) __LOC__ ;
477479
best_effort (fun () ->
478480
match e with
@@ -498,8 +500,7 @@ let read_request ?proxy_seen ~read_timeout ~total_timeout ~max_length fd =
498500
)
499501
| exc ->
500502
response_internal_error exc fd
501-
~extra:(escape (Printexc.to_string exc)) ;
502-
log_backtrace ()
503+
~extra:(escape (Printexc.to_string exc))
503504
) ;
504505
(None, None)
505506

ocaml/libs/http-lib/server_io.ml

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,13 @@ let establish_server ?(signal_fds = []) forker handler sock =
5353
let s, caller = Unix.accept ~cloexec:true sock in
5454
try ignore (forker handler s caller)
5555
with exc ->
56+
Backtrace.is_important exc ;
5657
(* NB provided 'forker' is configured to make a background thread then the
5758
only way we can get here is if Thread.create fails.
5859
This means we haven't executed any code which could close the fd therefore
5960
we should do it ourselves. *)
6061
debug "Got exception in server_io.ml: %s" (Printexc.to_string exc) ;
61-
log_backtrace () ;
62+
log_backtrace exc ;
6263
Unix.close s ;
6364
Thread.delay 30.0
6465
)

ocaml/libs/log/debug.ml

+9-4
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ let with_thread_associated ?client ?(quiet = false) desc f x =
258258
(* This function is a top-level exception handler typically used on fresh
259259
threads. This is the last chance to do something with the backtrace *)
260260
if not quiet then (
261+
(* It would seem that a Backtrace.is_important would be missing here.
262+
But in fact it has actually been called in [let result] above,
263+
so calling it again is not necessary.
264+
*)
261265
output_log "backtrace" Syslog.Err "error"
262266
(Printf.sprintf "%s failed with exception %s" desc
263267
(Printexc.to_string exn)
@@ -307,7 +311,7 @@ module type DEBUG = sig
307311

308312
val audit : ?raw:bool -> ('a, unit, string, string) format4 -> 'a
309313

310-
val log_backtrace : unit -> unit
314+
val log_backtrace : exn -> unit
311315

312316
val log_and_ignore_exn : (unit -> unit) -> unit
313317
end
@@ -344,9 +348,10 @@ functor
344348
)
345349
fmt
346350

347-
let log_backtrace () =
348-
let backtrace = Printexc.get_backtrace () in
349-
debug "%s" (String.escaped backtrace)
351+
let log_backtrace exn =
352+
let level = Syslog.Debug in
353+
if not (is_disabled Brand.name level) then
354+
log_backtrace_internal ~level exn ()
350355

351356
let log_and_ignore_exn f =
352357
try f ()

ocaml/libs/log/debug.mli

+9-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,15 @@ module type DEBUG = sig
7676
val audit : ?raw:bool -> ('a, unit, string, string) format4 -> 'a
7777
(** Audit function *)
7878

79-
val log_backtrace : unit -> unit
79+
val log_backtrace : exn -> unit
80+
(** [log_backtrace exn] logs the backtrace associated with [exn].
81+
Either this or {!Backtrace.is_important} must be the first statement in an exception handler,
82+
otherwise the backtrace may be overwritten (e.g. by formatting functions that internally raise and catch exceptions).
83+
84+
This has to be used instead of getting a new backtrace from Printexc if [Backtrace.is_important] was ever called,
85+
because that function stashes away the backtrace and then overwrites the current backtrace (to avoid duplicate frames in the stacktrace,
86+
when Backtrace.get is used).
87+
*)
8088

8189
val log_and_ignore_exn : (unit -> unit) -> unit
8290
end

ocaml/libs/log/test/dune

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
(executable
2+
(name log_test)
3+
(libraries log xapi-stdext-threads threads.posix xapi-backtrace))
4+
5+
(cram
6+
(deps log_test.exe))

ocaml/libs/log/test/log_test.ml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module D = Debug.Make (struct let name = Filename.basename __FILE__ end)
2+
3+
let m = Mutex.create ()
4+
5+
let a = [||]
6+
7+
let buggy () = a.(1) <- 0
8+
9+
let () =
10+
Printexc.record_backtrace true ;
11+
Debug.log_to_stdout () ;
12+
()
13+
|> Debug.with_thread_associated "main" @@ fun () ->
14+
try Xapi_stdext_threads.Threadext.Mutex.execute m buggy
15+
with e ->
16+
D.log_backtrace e ;
17+
D.warn "Got exception: %s" (Printexc.to_string e)

ocaml/libs/log/test/log_test.mli

Whitespace-only changes.

ocaml/libs/log/test/log_test.t

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
$ ./log_test.exe | sed -re 's/[0-9]+T[0-9:.]+Z//'
2+
[|error||0 |main|backtrace] Raised Invalid_argument("index out of bounds")
3+
[|error||0 |main|backtrace] 1/4 log_test.exe Raised at file ocaml/libs/log/test/log_test.ml, line 7
4+
[|error||0 |main|backtrace] 2/4 log_test.exe Called from file ocaml/libs/xapi-stdext/lib/xapi-stdext-pervasives/pervasiveext.ml, line 24
5+
[|error||0 |main|backtrace] 3/4 log_test.exe Called from file ocaml/libs/xapi-stdext/lib/xapi-stdext-pervasives/pervasiveext.ml, line 39
6+
[|error||0 |main|backtrace] 4/4 log_test.exe Called from file ocaml/libs/log/test/log_test.ml, line 14
7+
[|error||0 |main|backtrace]
8+
[| warn||0 |main|log_test.ml] Got exception: Invalid_argument("index out of bounds")
9+

ocaml/xapi-cli-server/cli_frontend.ml

-1
Original file line numberDiff line numberDiff line change
@@ -4052,7 +4052,6 @@ let rio_help printer minimal cmd =
40524052
in
40534053
printer (Cli_printer.PTable [recs])
40544054
| None ->
4055-
D.log_backtrace () ;
40564055
error "Responding with Unknown command %s" cmd ;
40574056
printer (Cli_printer.PList ["Unknown command '" ^ cmd ^ "'"])
40584057
in

ocaml/xapi-guard/lib/server_interface.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ let with_xapi ~cache ?(timeout = 120.) f =
5858
let serve_forever_lwt path callback =
5959
let conn_closed _ = () in
6060
let on_exn e =
61-
log_backtrace () ;
61+
log_backtrace e ;
6262
warn "Exception: %s" (Printexc.to_string e)
6363
in
6464
let stop, do_stop = Lwt.task () in

ocaml/xapi-guard/src/main.ml

+2-2
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ let make_message_switch_server () =
299299
(* best effort resume *)
300300
let* () =
301301
Lwt.catch (resume ~vtpm_read_write ~uefi_read_write) (fun e ->
302-
D.log_backtrace () ;
302+
D.log_backtrace e ;
303303
D.warn "Resume failed: %s" (Printexc.to_string e) ;
304304
Lwt.return_unit
305305
)
@@ -321,7 +321,7 @@ let main log_level =
321321
let old_hook = !Lwt.async_exception_hook in
322322
(Lwt.async_exception_hook :=
323323
fun exn ->
324-
D.log_backtrace () ;
324+
D.log_backtrace exn ;
325325
D.error "Lwt caught async exception: %s" (Printexc.to_string exn) ;
326326
old_hook exn
327327
) ;

ocaml/xapi-guard/test/xapi_guard_test.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ let () =
6868
let old_hook = !Lwt.async_exception_hook in
6969
Lwt.async_exception_hook :=
7070
fun exn ->
71-
D.log_backtrace () ;
71+
D.log_backtrace exn ;
7272
D.error "Lwt caught async exception: %s" (Printexc.to_string exn) ;
7373
old_hook exn
7474

ocaml/xapi-idl/gpumon/gpumon_interface.ml

-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ let gpu_err =
8787
def= gpu_errors
8888
; raiser=
8989
(fun e ->
90-
log_backtrace () ;
9190
let exn = Gpumon_error e in
9291
error "%s (%s)" (Printexc.to_string exn) __LOC__ ;
9392
raise exn

ocaml/xapi-idl/memory/memory_interface.ml

-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ let err =
8787
def= errors
8888
; raiser=
8989
(fun e ->
90-
log_backtrace () ;
9190
let exn = MemoryError e in
9291
error "%s (%s)" (Printexc.to_string exn) __LOC__ ;
9392
raise exn

0 commit comments

Comments
 (0)