fix #121 by initializing memory and avoid breaking strict aliasing rules #122
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix-121"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This was quite a journey, but the conclusion is that the issue comes from the following (simplified) code:
This converts parts of a
const unsigned char*into asph_u32*which gcc will consider to be pointing to different parts of memory (see: gcc strict aliasing rules).Considering these are different parts of memory, gcc was feeling OK to move around those reads and writes to the input buffer, resulting in garbage results. When
compress_smallwas called twice in the same function, gcc would apparently assume the reads were from the same piece of memory and group these, or something like that. Becausebmw32_closecallscompress_smallmultiple times in sequence, this would trigger the issue.This was fixed by using a fixed size memcpy, which the compiler should optimize nicely while understanding it is not OK to reorder those reads.
Additionally, bmw32_init/bmw64_init didn't properly initialize memory, which also caused some issues, this is also fixed here.
Fixes https://github.com/monacoinproject/monacoin/issues/121
@MagicalTux
I'll try syncing without a checkpoint.
Thanks!
@cryptcoin-junkey
Can I have you try that too?
@wakiyamap Thanks for the heads up.
@MagicalTux
Thank you for your contribution.
I read your patch.
In my first impression, I suppose it is reasonable.
And ... Could you give me a couple of days to understand the background of this issue ?
I'm going to read related issues like https://github.com/aidansteele/sphlib/issues/1 .
Sure no problem at all. I'll try to go a bit over what I found out, but each time I'm afraid it's a bit too complex.
https://github.com/MagicalTux/monadebug may be a good way to test this too, just go to the bug branch (the last commit contains the
memcpypatch) and attempt to compile & run this with a recent compiler. As far as I saw this bug will affect builds made with vanilla gcc 12 and more recent with -O1 or more (some linux distributions ship gcc with a lot of patches that may alter various behaviors).The official binary distribution of monacoin is not affected because it uses a older version of gcc:
GCC: (Ubuntu 8.4.0-1ubuntu1~18.04) 8.4.0Gcc rules say that different types of pointers (let's say a uint32_t* and a char*) cannot be pointing to the same part of memory (so called strict aliasing rules) which allows gcc to optimize functions like bmw's
compress_smallby moving reads together or doing one single read of the uint32_t* pointer even if a write happens on the char*, because gcc assumes those are different beasts.Knowing exactly what goes wrong would require reading the asm gcc generates, but bmw is a rather complex algorithm and quite frankly I'm not sure there is anything useful we can learn from that we do not already know.
I tried to find various methods to alleviate this but the recommended method is a
memcpywith fixed size, and let the compiler optimize things. It looks like it should not be doing anything, yet this bug disappears.A nit-pick. I bear "the section 6.2.6.1 in ISO 9899:2011" in mind.
@ -609,6 +609,7 @@ static const sph_u32 final_s[16] = {static voidbmw32_init(sph_bmw_small_context *sc, const sph_u32 *iv){memset(sc, 0, sizeof(sph_bmw_small_context));I think it can have some side effects using
memsetonly (even if they are rare case).How about this?
Same above.
Thank you for your detailed note. @MagicalTux
I figured out the background of this issue.
I remember some older GCCs made warnings around strict aliasing rules.
And I'm not surprised if newer GCCs optimize code using an aliasing.
I raised some suggestions for this PR. You can reject them if you feel "too nit-picking".
Anyway I'll accept this patch.
@ -609,6 +609,7 @@ static const sph_u32 final_s[16] = {static voidbmw32_init(sph_bmw_small_context *sc, const sph_u32 *iv){memset(sc, 0, sizeof(sph_bmw_small_context));I don't think you can do that.
This would work but look less nice:
I feel however the memset is more than appropriate considering the structure of
sph_bmw_small_contextandsph_bmw_big_contextonly contains integers and arrays of chars or integers, and those structures aren't going to change anytime soon.@ -609,6 +609,7 @@ static const sph_u32 final_s[16] = {static voidbmw32_init(sph_bmw_small_context *sc, const sph_u32 *iv){memset(sc, 0, sizeof(sph_bmw_small_context));I see.
I agree with your comment. I reject my suggestion.
LGTM