Skip to content

Commit 524791c

Browse files
committed
CA-409628: Do not lose the original backtrace in log_backtrace
There are 2 log_backtrace functions in the Debug module, one of them prints the full backtrace, the other one prints the backtrace just from the last time Backtrace.is_important got called, i.e. it drops the *important* part of the backtrace. Delete the buggy function and replace it with a function that always takes an exception, so that we can look up any stashed backtrace. There are good reasons why Backtrace.is_important wipes the current backtrace after it stashes it away (destroying the important part): * if it didn't, then every time Backtrace.is_important got called we'd get the same backtrace prefix appeneded multiple times * for cross-process/language backtraces to work you need to use Backtrace.get to retrieve the stashed backtrace when printing, and not use the OCaml one directly from Printexc. But that means you need to be disciplined in how you deal with backtraces: * catching and reraising the same exception with the same backtrace using Printexc.rawbacktrace is fine * the first statement in an exception handler needs to be either Backtrace.is_important, or Debug.log_backtrace (which calls is_important internally) Using Printexc.get_backtrace/Printexc.get_rawbacktrace for printing purposes has to be avoided if we ever called Backtrace.is_important. The updated unit test shows that we properly print line 8 as the source of the exception now. This fixes backtraces from xcp-rrdd. Signed-off-by: Edwin Török <[email protected]>
1 parent bfea6f3 commit 524791c

24 files changed

+144
-130
lines changed

ocaml/libs/http-lib/dune

Lines changed: 92 additions & 98 deletions
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

Lines changed: 4 additions & 2 deletions
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:""
@@ -497,9 +498,10 @@ let read_request ?proxy_seen ~read_timeout ~total_timeout ~max_length fd =
497498
(Unix.error_message a) b c
498499
)
499500
| exc ->
501+
Backtrace.is_important exc ;
500502
response_internal_error exc fd
501503
~extra:(escape (Printexc.to_string exc)) ;
502-
log_backtrace ()
504+
log_backtrace exc
503505
) ;
504506
(None, None)
505507

ocaml/libs/http-lib/server_io.ml

Lines changed: 2 additions & 1 deletion
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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ module type DEBUG = sig
307307

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

310-
val log_backtrace : unit -> unit
310+
val log_backtrace : exn -> unit
311311

312312
val log_and_ignore_exn : (unit -> unit) -> unit
313313
end
@@ -344,9 +344,10 @@ functor
344344
)
345345
fmt
346346

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

351352
let log_and_ignore_exn f =
352353
try f ()

ocaml/libs/log/debug.mli

Lines changed: 9 additions & 1 deletion
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/log_test.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ let () =
1313
|> Debug.with_thread_associated "main" @@ fun () ->
1414
try Xapi_stdext_threads.Threadext.Mutex.execute m buggy
1515
with e ->
16-
D.log_backtrace () ;
16+
D.log_backtrace e ;
1717
D.warn "Got exception: %s" (Printexc.to_string e)

ocaml/libs/log/test/log_test.t

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
$ ./log_test.exe | sed -re 's/[0-9]+T[0-9:.]+Z//'
2-
[|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 14, characters 9-60\n
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]
38
[| warn||0 |main|log_test.ml] Got exception: Invalid_argument("index out of bounds")
49

ocaml/xapi-cli-server/cli_frontend.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4052,7 +4052,7 @@ let rio_help printer minimal cmd =
40524052
in
40534053
printer (Cli_printer.PTable [recs])
40544054
| None ->
4055-
D.log_backtrace () ;
4055+
D.log_backtrace Not_found ;
40564056
error "Responding with Unknown command %s" cmd ;
40574057
printer (Cli_printer.PList ["Unknown command '" ^ cmd ^ "'"])
40584058
in

ocaml/xapi-guard/lib/server_interface.ml

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 2 deletions
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
) ;

0 commit comments

Comments
 (0)