Bug 5517 – SEGV: assert(false) in release mode

Status
RESOLVED
Resolution
WORKSFORME
Severity
normal
Priority
P2
Component
druntime
Product
D
Version
D2
Platform
x86
OS
Linux
Creation time
2011-02-02T02:53:53Z
Last change time
2020-03-21T03:56:31Z
Keywords
wrong-code
Assigned to
No Owner
Creator
Iain Buclaw

Comments

Comment #0 by ibuclaw — 2011-02-02T02:53:53Z
void main() { assert(0); } When compiled in release mode, the instructions outputted by DMD cause a segfault and exit code 139 (on Linux). I expected an abort and exit code 134. $ dmd abort.d -release $ ./abort Segmentation fault (core dumped) $ echo $? 139 Disassembly of section .text._Dmain: 00000000 <_Dmain>: void main() { 0: 55 push %ebp 1: 8b ec mov %esp,%ebp 3: f4 hlt Regards
Comment #1 by clugdbug — 2011-03-02T07:24:27Z
The generated code is correct. Looks like a druntime bug to me. My guess is that it's segfaulting while processing the HLT. Reassigning to druntime.
Comment #2 by clugdbug — 2011-03-03T23:46:40Z
Some further detail: 0xF4, the HLT opcode, is a privileged instruction; it doesn't actually get executed. Instead, a Privileged Instruction hardware exception is raised. I would expect Linux to turn this into SIGILL. So I would expect an exit code of 132, if nothing is done to process it. There is no way it should give a SEGV. On Windows, druntime checks the offending instruction, and if it is HLT, it is identified as a runtime assert(0).
Comment #3 by ibuclaw — 2011-03-05T05:09:41Z
(In reply to comment #2) > Some further detail: 0xF4, the HLT opcode, is a privileged instruction; it > doesn't actually get executed. Instead, a Privileged Instruction hardware > exception is raised. I would expect Linux to turn this into SIGILL. So I would > expect an exit code of 132, if nothing is done to process it. There is no way > it should give a SEGV. > If I was going off what I recall, I would have said that when a program receives the hlt instruction; it just stops executing instructions until there is an interrupt (in other words it just floats off in an undefined space). But I've looked it up, and HLT invokes SIGSEGV signal afterall... > On Windows, druntime checks the offending instruction, and if it is HLT, it is > identified as a runtime assert(0). So on Windows, the runtime has a bespoke signal handler?
Comment #4 by clugdbug — 2011-03-05T18:28:56Z
(In reply to comment #3) > (In reply to comment #2) > > Some further detail: 0xF4, the HLT opcode, is a privileged instruction; it > > doesn't actually get executed. Instead, a Privileged Instruction hardware > > exception is raised. I would expect Linux to turn this into SIGILL. So I would > > expect an exit code of 132, if nothing is done to process it. There is no way > > it should give a SEGV. > > > > If I was going off what I recall, I would have said that when a program > receives the hlt instruction; it just stops executing instructions until there > is an interrupt (in other words it just floats off in an undefined space). That's what it does in ring 0 (ie, inside the kernel). But outside the kernel, it's a privileged instruction, which generates a hardware 'illegal instruction' interrupt. > But I've looked it up, and HLT invokes SIGSEGV signal afterall... Wow. Then Linux is just wrong. It should definitely be SIGILL. There's no memory access violation. > > On Windows, druntime checks the offending instruction, and if it is HLT, it is > > identified as a runtime assert(0). > > So on Windows, the runtime has a bespoke signal handler? Everything you'd call a signal on Linux, is just a system exception on Windows. D's exception handling uses the exact same mechanism. On all platforms, main is surrounded by a catch(...) which displays error messages from any uncaught exceptions. On Windows, this includes hardware faults as well as D-generated exceptions. The impression I get from the little I know of Unix signal handling, it seems to be one of the few things that Unix got horribly wrong. Windows structured exception handling is brilliant (albeit almost completely undocumented!) I am extremely impressed with it. For example, there's no restrictions on what you can do inside an exception handler. So there's probably more challenges for the *nix implementation of a lot of this stuff.
Comment #5 by ibuclaw — 2011-03-06T04:49:31Z
(In reply to comment #4) > (In reply to comment #3) > > (In reply to comment #2) > > > Some further detail: 0xF4, the HLT opcode, is a privileged instruction; it > > > doesn't actually get executed. Instead, a Privileged Instruction hardware > > > exception is raised. I would expect Linux to turn this into SIGILL. So I would > > > expect an exit code of 132, if nothing is done to process it. There is no way > > > it should give a SEGV. > > > > > > > If I was going off what I recall, I would have said that when a program > > receives the hlt instruction; it just stops executing instructions until there > > is an interrupt (in other words it just floats off in an undefined space). > > That's what it does in ring 0 (ie, inside the kernel). But outside the kernel, > it's a privileged instruction, which generates a hardware 'illegal instruction' > interrupt. > > > > But I've looked it up, and HLT invokes SIGSEGV signal afterall... > > Wow. Then Linux is just wrong. It should definitely be SIGILL. There's no > memory access violation. > Well, signals are != exceptions. :) > > > On Windows, druntime checks the offending instruction, and if it is HLT, it is > > > identified as a runtime assert(0). > > > > So on Windows, the runtime has a bespoke signal handler? > > Everything you'd call a signal on Linux, is just a system exception on Windows. > D's exception handling uses the exact same mechanism. > On all platforms, main is surrounded by a catch(...) which displays error > messages from any uncaught exceptions. > On Windows, this includes hardware faults as well as D-generated exceptions. > > The impression I get from the little I know of Unix signal handling, it seems > to be one of the few things that Unix got horribly wrong. Windows structured > exception handling is brilliant (albeit almost completely undocumented!) I am > extremely impressed with it. For example, there's no restrictions on what you > can do inside an exception handler. > So there's probably more challenges for the *nix implementation of a lot of > this stuff. There are 2 alternatives I can think of instead of using 'hlt' 1) Throw an exception (goes against release mode). 2) Raise an alternate interrupt. Like 'hlt', these are single byte opcodes. - int 0x3; #BP will invoke SIGTRAP signal handler. - ud2; #UD will invoke SIGILL signal handler. Regards
Comment #6 by clugdbug — 2011-03-06T07:49:39Z
(In reply to comment #5) > (In reply to comment #4) > > (In reply to comment #3) > > > (In reply to comment #2) > > > > Some further detail: 0xF4, the HLT opcode, is a privileged instruction; it > > > > doesn't actually get executed. Instead, a Privileged Instruction hardware > > > > exception is raised. I would expect Linux to turn this into SIGILL. So I would > > > > expect an exit code of 132, if nothing is done to process it. There is no way > > > > it should give a SEGV. > > > > > > > > > > If I was going off what I recall, I would have said that when a program > > > receives the hlt instruction; it just stops executing instructions until there > > > is an interrupt (in other words it just floats off in an undefined space). > > > > That's what it does in ring 0 (ie, inside the kernel). But outside the kernel, > > it's a privileged instruction, which generates a hardware 'illegal instruction' > > interrupt. > > > > > > > But I've looked it up, and HLT invokes SIGSEGV signal afterall... > > > > Wow. Then Linux is just wrong. It should definitely be SIGILL. There's no > > memory access violation. > > > > Well, signals are != exceptions. :) Even if you have that opinion, HLT is not a segfault. If Linux does the stupidity of treating privileged instruction violations as segfaults (are you CERTAIN it does? It's really hard to believe), then we shouldn't try to cover up that stupidity by pretend that SIGSEGV only ever means a segfault. Does it really report a segfault on this code? void main() { asm { hlt; } } > There are 2 alternatives I can think of instead of using 'hlt' > > 1) Throw an exception (goes against release mode). > > 2) Raise an alternate interrupt. Like 'hlt', these are single byte opcodes. > > - int 0x3; #BP will invoke SIGTRAP signal handler. > - ud2; #UD will invoke SIGILL signal handler. > > Regards The first one won't work -- it defeats the ENTIRE PURPOSE of the feature. The second one's really, really wrong, and I can't see that it would even be an improvement.
Comment #7 by issues.dlang — 2011-03-06T15:05:47Z
Well, if I run tho short program that Don provides there on my Linux box, it definitely prints out "Segmentation fault" on the command line. So, whatever signals end up being sent, that's how bash reports it.
Comment #8 by ibuclaw — 2011-03-09T12:10:47Z
Some notes: - sigaction can be used to catch signals - signals caught have a packed struct with some simple information attached. - si_signo: Signal number (ie: SIGSEGV = 11). - si_errno: Errno value (unused on Linux). - si_code: Signal code to indicate why this signal was sent. - The two possible signal codes related to SIGSEGV signals are: - SEGV_MAPERR: address not mapped to object - SEGV_ACCERR: invalid permissions for mapped object - General purpose signal codes tell how the signal was raised: - SI_USER: kill, sigsend or raise - SI_KERNEL: The kernel - SI_QUEUE: sigqueue - SI_TIMER: timer expired - SI_MESGQ: mesq state changed - SI_ASYNCIO: AIO completed - SI_SIGIO: queued SIGIO - HLT is emitted from the kernel (SI_KERNEL). Here is a quick sample program: import core.stdc.stdio; import core.sys.posix.unistd; import core.sys.posix.signal; extern(C) void signal_handler(int signum, siginfo_t* info, void* ptr) { if (info.si_signo == SIGSEGV && info.si_code == SI_KERNEL) { fprintf(stderr, "assert(0) or SEGV sent by kernel\n"); _exit(signum); } } void main() { sigaction_t action; action.sa_flags = SA_SIGINFO | SA_RESETHAND; action.sa_sigaction = &signal_handler; sigaction(SIGSEGV, &action, null); assert(0); }
Comment #9 by bugzilla — 2012-01-28T15:19:50Z
Quoth the spec: "The expression assert(0) is a special case; it signifies that it is unreachable code. Either AssertError is thrown at runtime if it is reachable, or the execution is halted (on the x86 processor, a HLT instruction can be used to halt execution). The optimization and code generation phases of compilation may assume that it is unreachable code." It's working as designed. dlang.org/expression.html#AssertExpression
Comment #10 by atankeev — 2013-03-01T12:31:47Z
*** Issue 9134 has been marked as a duplicate of this issue. ***
Comment #11 by shachar — 2015-06-25T12:36:02Z
Quoth the same spec: void main() { assert(0, "an" ~ "error message"); } When compiled and run, it will produce the message: Error: AssertError Failure test.d(3) an error message In practice, all it produces is: Segmentation fault (core dumped) We can clearly see that dmd, hence, violates the spec. Also, the following statement in the spec is demonstrable wrong: (on the x86 processor, a HLT instruction can be used to halt execution) This is untrue (or, at the very least, highly misleading) for all user space programming purposes.
Comment #12 by issues.dlang — 2015-06-28T01:54:22Z
(In reply to Shachar Shemesh from comment #11) > Quoth the same spec: > void main() > { > assert(0, "an" ~ "error message"); > } > > When compiled and run, it will produce the message: > Error: AssertError Failure test.d(3) an error message > > In practice, all it produces is: > Segmentation fault (core dumped) It produces a message when compiled without -release. With -release, it becomes a HLT instruction, so no message. > We can clearly see that dmd, hence, violates the spec. Walter quoted the spec. It's correct. If somewhere is in the spec doesn't match that, then it should be updated. I would not expect the compiler's behavior to be changed. Why have you reopened this issue? If the spec needs to be updated because it's unclear somewhere, then open a new issue for that. But Walter has made it clear this is acting as intended, and I really don't see a problem with it.
Comment #13 by issues.dlang — 2015-06-28T01:56:29Z
On a side note, I just tried this on FreeBSD with -release, and I get a "bus error" rather than a segmentation fault, so the behavior there is slightly different from Linux, if that matters at all. Either way, it's killing the program like it's supposed to.
Comment #14 by shachar — 2015-06-28T05:17:58Z
(In reply to Jonathan M Davis from comment #13) > Either way, it's killing the program like it's supposed to. But it is not printing the message. Yes, I think the spec is wrong (and, at least when opening a new bug, Bugzilla is directing me to use this category, of bugs in the compiler, to report minor problems with the spec), but that is not the core of this issue, and not the reason I reopened it. The core of this issue is that "assert(false, "message")" is defined by the spec to print the message and abort even in release mode. In practice, it aborts (in an incorrect way, IMHO), but does not print the message. And this is a bug in the compiler. Shachar
Comment #15 by issues.dlang — 2015-06-28T08:15:32Z
(In reply to Shachar Shemesh from comment #14) > (In reply to Jonathan M Davis from comment #13) > > Either way, it's killing the program like it's supposed to. > > But it is not printing the message. > > And this is a bug in the compiler. Walter has made it clear that this is _not_ a bug in the compiler. The compiler is not required to print a message in release mode. Rather, it quite specifically inserts an HLT instruction - which isn't going to print a message. And I very much doubt that the spec says anywhere that assertions print out anything in release mode, since all other assertions get compiled out in release mode, and the spec clearly states that assert(0) gets turned into a HLT instruction in release mode. So, while I can see why you might want a message in release mode, I don't see how you can claim that it's a compiler bug that assert(0, "message") doesn't print one. Where in the spec gives you the idea that assert(0, "message") would print out anything in release mode? If it didn't print something when you compiled _without_ -release, then that would be a bug, but if you compile with -release, you get a HLT instruction and no message. And even if there is somewhere in the spec that implies that assert(0, "message") is supposed to print a message even in release mode, Walter has made it clear that the compiler is doing what it's supposed to be doing, so if the spec isn't clear enough on the matter, then the spec needs to be updated. The compiler's current behavior is not going to be changed.
Comment #16 by k.hara.pg — 2015-07-25T01:07:54Z
(In reply to Shachar Shemesh from comment #14) > But it is not printing the message. Recently I opened a PR to update core language specifications. https://github.com/D-Programming-Language/dlang.org/pull/1040 In that, I modified the description about the assert(0, message) in order to follow the current compiler behavior. https://github.com/9rnsr/dlang.org/commit/4f5da7958fe2665b57521b5d897b00ed57cdab48 If you interested, please join the review of the PR.
Comment #17 by b2.temp — 2019-06-06T22:00:56Z
SEGILL, as expected, is generated nowadays, the message is another problem.