Skip to content

Conversation

MisterDA
Copy link

@MisterDA MisterDA commented Mar 3, 2025

On Windows, "negative" exit codes are probably NTSTATUS values. For example, if a program accesses an invalid memory location, Unix sends a SIGSEGV signal which, if unhandled, will terminate the process (setting some kind of non-zero exit code - for example, Linux sets the exit code to 128 + signal number to give a fairly memorable 139). In the equivalent scenario, Windows throws an EXCEPTION_ACCESS_VIOLATION which, if handled by the default exception handler, will terminate the process with exit code STATUS_ACCESS_VIOLATION. These codes are large negative numbers, which are not terribly memorable in decimal, so for negative exit codes we instead display them in hexadecimal as 0xc0000005 is slightly more memorable than -1073741819.

I saw this error code while running odig log -e, but maybe it doesn't originate from a b0 call.

I'm pushing for this change throughout the stack (already patched Dune, opam, and ocaml-ci).

On Windows, "negative" exit codes are probably NTSTATUS values. For
example, if a program accesses an invalid memory location, Unix sends
a SIGSEGV signal which, if unhandled, will terminate the
process (setting some kind of non-zero exit code - for example, Linux
sets the exit code to 128 + signal number to give a fairly memorable
139). In the equivalent scenario, Windows throws an
EXCEPTION_ACCESS_VIOLATION which, if handled by the default exception
handler, will terminate the process with exit code
STATUS_ACCESS_VIOLATION. These codes are large negative numbers, which
are not terribly memorable in decimal, so for negative exit codes we
instead display them in hexadecimal as 0xc0000005 is slightly more
memorable than -1073741819.
@MisterDA MisterDA force-pushed the win32-exit-status branch from 626f114 to a0e4d16 Compare March 3, 2025 11:29
| `Exited n -> Fmt.pf ppf "@[exited [%d]@]" n
| `Exited n ->
if Sys.win32 && n < 0 then
Fmt.pf ppf "@[exited [0x%08lx]@]" (Int32.of_int n)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.

I'm rather add an Fmt.exit_code to b0__fmt. I'm pretty sure I must be printing exit code in other places. I'm not asking you to do the work because b0_std is gradually becoming a vendoring of more which I intend to distribute separately at some point.

But I do have a question here. Why to you convert to int32 rather than simply format with "0x%08x" ?

Copy link
Author

@MisterDA MisterDA Mar 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I do have a question here. Why to you convert to int32 rather than simply format with "0x%08x" ?

Ouch, I copy-pasted this code from a hacker without thinking about it hard enough. NTSTATUS is a long, so a signed, 32-bits integer. The error codes are in the 0xC0000000 - 0xFFFFFFFF range, so on 64-bits platforms there shouldn't be any problem using an OCaml int, but on 32-bits, taking into account the OCaml bit tagging, as the exit code will be stored into a 31-bits integer, do we lose 1 bit? my brain just crashed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but on 32-bits, taking into account the OCaml bit tagging, as the exit code will be stored into a 31-bits integer, do we lose 1 bit? my brain just crashed.

Yes on a 32-bit platform you'd lose a bit, in the error code part of the encoding

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there's nothing we can do for 32-bit platforms anyway, the exit code will always be incorrect. Maybe we could print the last bit as ?.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes perhaps adding a "?" at the right place apt.

That being said my own head is exploding now. If I look at the table here the codes seem to be in the lsb (which is not what I read in the chart I linked to). So it's not the code that would end up being botched on a 32-bit platform but rather one of the severity bits.

Copy link
Author

@MisterDA MisterDA Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've given this a lot of thoughts and this is the result I came up with. The exit codes on the C side are 32-bit unsigned integers, so on OCaml 64 bits there's nothing particular to be done. On OCaml 32-bit we loose the MSB.
If we assume that no sane application would set the two most significant bits, and that they usually indicate an NTSTATUS error, we can check for the 2nd most significant bit. If it is set, we may assume that the exit code represented an NTSTATUS, and set the MSB. This gives:

let conv n =
  if Sys.win32 then
    let n = Int32.of_int n in
    if Sys.word_size == 32 then
      (* We've lost the MSB converting from C to OCaml. We suppose
         that no sane application would set the two most significant
         bits, that usually indicate a STATUS_SEVERITY_ERROR. Check
         for the second MSB. *)
      let msb = Int32.shift_left 1l 31 in
      if Int32.(compare (logand (shift_right n 30) 1l) 1l) == 0 then
        Printf.sprintf "0x%08lX" (Int32.logor n msb)
      else
        (* Technically, [Int32.(logor n msb)] is also possible. *)
        Int32.(logand n (lognot msb) |> to_string)
    else
      let status_severity_error = Int32.(shift_left 0x3l 30) in
      if Int32.(unsigned_compare (logand n status_severity_error) 0l) != 0 then
        (* Usually indicates an NTSTATUS in the error range. *)
        Printf.sprintf "0x%08lX" n
      else
        Int32.to_string n
  else
    string_of_int n

Interestingly, if an executable exits with 0x80000000U, 32-bit OCaml code comparing the result with 0 will consider that the process exited successfully.

I've already pushed (broken?) patches to opam and dune with the code I've first submitted. If you adopt this new idea or manage to shorten it, I'll update my previous submissions with the new code, with your permission.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MisterDA sorry to have lost the ball on this. Are you positive that conv is what we want ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not totally confident that the 32-bits code is correct. It seems to behave correctly but I'm not sure I fully grasp what's happening to OCaml integers and shifts and signed/unsigned conversion…

@dbuenzli
Copy link
Contributor

dbuenzli commented Mar 3, 2025

I saw this error code while running odig log -e, but maybe it doesn't originate from a b0 call.

In fact that comes out of a B0_memo log file so it's printed by this (and the reason why I want to add something to B0_std.Fmt).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants