Intriguing assembly for comparing std::optional of primitive types

≡放荡痞女 提交于 2019-12-03 22:16:21

In x86 asm, the worst that happens is that a single register has an unknown value (or you don't know which of two possible values it has, old or new, in case of possible memory-ordering). But if your code doesn't depend on that register value, you're fine, unlike in C++. C++ UB means your whole program is in theory completely hosed after one signed-integer overflow, and even before that along code-paths that the compiler can see will lead to UB. Nothing like that ever happens in asm, at least not in unprivileged user-space code.

(There might be a few things you can do to basically cause system-wide unpredictable behaviour in the kernel, by setting control registers in weird ways or putting inconsistent things into page tables or descriptors, but that's not going to happen from something like this, even if you were compiling kernel code.)


Some ISAs have "unpredictable behaviour", like early ARM if you use the same register for multiple operands of a multiply, the behaviour is unpredictable. IDK if this allows breaking the pipeline and corrupting other registers, or if it's restricted to just an unexpected multiply result. The latter would be my guess.

Or MIPS, if you put a branch in the branch-delay slot, the behaviour is unpredictable. (Handling exceptions is messy because of branch-delay slots...). But presumably there are still limits and you can't crash the machine or break other processes (in a multi-user system like Unix, it would be bad if an unprivileged user-space process could break anything for other users).

Very early MIPS also had load-delay slots, and multiply delay slots: you couldn't use the result of a load in the next instruction. Presumably you might get the old value of the register if you read it too early, or maybe just garbage. MIPS = Minimally Interlocked Pipeline Stages; they wanted to offload the stalling to software, but it turned out that adding a NOP when the compiler couldn't find anything useful to do next bloated binaries and led to slower overall code vs. having the hardware stall when necessary. But we're stuck with branch-delay slots because removing them would change the ISA, unlike relaxing a restriction on something early software didn't do.

There are no trap values in x86 integer formats, so reading and comparing uninitialized values generates unpredictable truth/false values and no other direct harm.

In a cryptographic context, the state of the uninitialized values causing a different branch to be taken could leak into timing information leaking or other side-channel attacks. But cryptographic hardening is probably not what you are worried about.

The fact that gcc does uninitialized reads when it doesn't matter if the read gives the wrong value doesn't mean it will do it when it matters.

I would not be so sure that it is caused by compiler error. Possibly there is some UB in Your code which allows compiler to optimize more aggressivelly Your code. Anyway, to the questions:

  1. UB is not an issue in assembly. In most general case, what is left under the address You are refering to will be read. Of course most OSes fill memory pages before giving them to program, but your variable most probably resides on stack, so most probably it contains garbage data. Soo, as long You are ok with random data comparison (which is quite bad, as may spuriously give different results) assembly is valid
  2. Most probably it is syndrome of reversed comparison
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!