Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Foxfire_
Nov 8, 2010

Schrodenger's solution:
code:
void Crash()
{
    volatile int* volatile zero = NULL;
    *zero = 0xDEAD;
}
Compiler can't assume anything about the value read back from `zero` at compile time.

Still permitted to turn it into
code:
void Crash()
{
    volatile int* volatile zero = NULL;
    int* zero2 = zero;
    if (zero2 == NULL)
    {
        printf("gently caress you, buddy");
    }
    else
    {
        *zero2 = 0xDEAD;
    }
}
Actually Good (tm) solution:
code:
Crash:
    movi 0, eax
    movi 0xDEAD, ebx
    mov ebx, [eax]
Just write the three instructions you're trying to write directly

Foxfire_ fucked around with this message at 22:25 on Mar 7, 2020

Adbot
ADBOT LOVES YOU

Vanadium
Jan 8, 2005

How do systems with stuff mapped at 0 even work? C++ still says that a pointer created from a zero integer constant is a null pointer that you can't dereference, do you have to go out of your way to decrement a pointer to address 1 or something?

Foxfire_
Nov 8, 2010

Either never touch it from C/C++, or access it via a global symbol that you tell the linker to put there

Private Speech
Mar 30, 2011

I HAVE EVEN MORE WORTHLESS BEANIE BABIES IN MY COLLECTION THAN I HAVE WORTHLESS POSTS IN THE BEANIE BABY THREAD YET I STILL HAVE THE TEMERITY TO CRITICIZE OTHERS' COLLECTIONS

IF YOU SEE ME TALKING ABOUT BEANIE BABIES, PLEASE TELL ME TO

EAT. SHIT.


Yeah basically just linker magic, it's common to have significant amount of linker tweaks on RTOS/MCU embedded platforms (unix ones not so much).

Even more fun is when you have two interrupt vectors for the purpose of firmware flashing, and you have to swap them.

e: Or, well, firmware flashing in general is quite fun, since the flash tends to be only writable in pages and there's all sorts of fun to be had there. Especially if you don't have enough space to keep two copies of the program active at once (you write a minimalist flashing routine first, then swap into that, overwrite the main program, and then swap back).

Private Speech fucked around with this message at 06:02 on Mar 8, 2020

Xarn
Jun 26, 2015

This was a good post.

I'll also add that the worst case failure mode of raise(SIGSEGV) is that it does nothing. The worst case of explicitly dereferencing a null pointer is that it rewrites half your code to remove every branch that can reach it, and then compiles that. :v:

I am quite partial to this https://godbolt.org/z/P3qvPN as an example of how the optimizer can use "UB is impossible" reasoning to completely mangle your code.

bolind
Jun 19, 2005



Pillbug
This has yielded lots of interesting discussion, thanks guys.

The original code was, best we can tell, designed to cause a crash if some 3rd party library failed. Original fail mode was to call exit() which we don’t have.

The function is poorly documented and the guy is no longer around. Of course.

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

Xerophyte posted:

Anyhow, for the purpose of checking that your bespoke diagnostic signal callback works, explicitly calling the signal handler with raise should to my knowledge be as good as forcing an actual invalid memory access.

Does this mean that all the signal context is filled in appropriately, like the faulting address and similar? Not that I can figure out what exactly “appropriately” means in this situation, but one curiosity at a time!

E: most of my experience with trapping SEGV and friends is working with crash-reporting systems which depend a lot on that context being correct in order to generate useful reports. I once tried to build a memory mapping system that loaded stuff in manually on fault to unmapped areas, but the ergonomics were really bad, as a person who wasn’t 17-year-old me might have imagined after even brief consideration.

Subjunctive fucked around with this message at 06:24 on Mar 12, 2020

Xerophyte
Mar 17, 2008

This space intentionally left blank

Subjunctive posted:

Does this mean that all the signal context is filled in appropriately, like the faulting address and similar? Not that I can figure out what exactly “appropriately” means in this situation, but one curiosity at a time!

I cheerfully assumed that it would be filled in but it looks like I am about to be annoyed as someone, specifically me, informs me that windows does in fact do this in some fun way. I assumed most platforms would have raise simply call the kernel's error handling routine so it could proceed exactly as if the CPU had trapped, filling in some reasonable context and allowing raise to be used to test your error handling code.

A quick test on windows shows that I was talking out of my rear end.

C++ code:
#include <csignal>
#include <iostream>
#include <Windows.h>

LONG WINAPI winApiSignalHandler(EXCEPTION_POINTERS* info) {
  std::cerr << "Win Signal: " << info->ExceptionRecord->ExceptionCode << std::endl;
  return EXCEPTION_EXECUTE_HANDLER;
}

void cSignalHandler(int signal) {
  std::cerr << "C Signal: " << signal << std::endl;
}

int main() {
  SetUnhandledExceptionFilter(winApiSignalHandler);
  std::signal(SIGSEGV, cSignalHandler);

  // Raise an access violation directly or by trying to force a write to 0.
  std::raise(SIGSEGV);
  // RaiseException(EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, 0);
  // *(int*)nullptr = 0xDEAD;
}
Writing to nullptr or using RaiseException results in windows calling both handlers. raise bypasses the kernel and only calls the handler set with std::signal. RaiseException can fully emulate all the context of an actual fault, but you need to provide the "faulting" address and read/write flag yourself in the call.

In hindsight I'm not sure why expected otherwise: signals are platform-specific behavior, so I should expect to write platform-specific code.

Xerophyte fucked around with this message at 12:41 on Mar 12, 2020

LLSix
Jan 20, 2010

The real power behind countless overlords

Just found a bug in an Unreal (UE4.22.3) program so weird I have to share it.

code:
ScoreTextPointer->SetText(FString::FromInt(MAXIMUM_SCORE));
The above line of code was causing null pointer errors the second time the unreal program that included it ran. It ran and worked correctly (displayed the expected text value to the user) the first time the program was run.

Edit: I should add that the line of code I posted causes nullptr errors in a different class. Specifically, in an instance of a class that is, like ScoreTextPointer, a member variable of the class that the posted code snippet is from.

Can anyone guess what was wrong with that line? I only figured it out by reverting then re-adding the commit that introduced the bug a line at a time.

LLSix fucked around with this message at 03:12 on Mar 13, 2020

Volguus
Mar 3, 2009

LLSix posted:

Just found a bug in an Unreal (UE4.22.3) program so weird I have to share it.

code:
ScoreTextPointer->SetText(FString::FromInt(MAXIMUM_SCORE));
The above line of code was causing null pointer errors the second time the unreal program that included it ran. It ran and worked correctly (displayed the expected text value to the user) the first time the program was run.

Can anyone guess what was wrong with that line? I only figured it out by reverting then re-adding the commit that introduced the bug a line at a time.

What is it? ScoreTextPointer is nullptr? That's the only thing that can be null there, as FString should return a valid string regardless of what value you pass it, as it would be quite dumb to say if(score == INT_MAX) return nullptr .

LLSix
Jan 20, 2010

The real power behind countless overlords

Volguus posted:

What is it? ScoreTextPointer is nullptr? That's the only thing that can be null there, as FString should return a valid string regardless of what value you pass it, as it would be quite dumb to say if(score == INT_MAX) return nullptr .

No, but that is a sane and sensible guess.

I should add that the line of code I posted causes nullptr errors in a different class. Specifically, in an instance of a class that is, like ScoreTextPointer, a member variable of the class that the code is in.

Absurd Alhazred
Mar 27, 2010

by Athanatos

LLSix posted:

No, but that is a sane and sensible guess.

I should add that the line of code I posted causes nullptr errors in a different class. Specifically, in an instance of a class that is, like ScoreTextPointer, a member variable of the class that the code is in.

Is it also in a different thread? I've seen that happen when an exception is thrown in one thread, starting the whole program getting destroyed, only for another thread to try to still access data that has just been deallocated. Sometimes you have to do dumb null checks just to get to the actual exception.

Brownie
Jul 21, 2007
The Croatian Sensation
This is perhaps a dumb question, but whats the best way to throw an error with exceptions turned off? I'm writing code that in certain cases I want to fail completely (because it's only reachable due to programmer error) with a helpful debug message (in debug) and also still bomb in a Release build as well.

The code I've been using thus far comes from the official D3D11/D3D12 starter projects, and uses __debugbreak(), along with some helpful printing statements. If I only care about Windows, should I just keep using this?

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Brownie posted:

This is perhaps a dumb question, but whats the best way to throw an error with exceptions turned off? I'm writing code that in certain cases I want to fail completely (because it's only reachable due to programmer error) with a helpful debug message (in debug) and also still bomb in a Release build as well.

The code I've been using thus far comes from the official D3D11/D3D12 starter projects, and uses __debugbreak(), along with some helpful printing statements. If I only care about Windows, should I just keep using this?

Comedy option setjmp and longjmp

nielsm
Jun 1, 2009



Have a FatalError() function you can call with whatever message. If you end up needing to, you can then have different implementations of that per platform.
The most basic would just be calling abort(), maybe write the message to some kind of log file. On Windows you could maybe consider using the MessageBox() function to display something, just be careful about using it if you do things like fullscreen or locking the mouse cursor position. Also be careful if you have threads dong other things.

But if you just want your process to die right now, abort() is the way to do it.

more falafel please
Feb 26, 2005

forums poster

LLSix posted:

No, but that is a sane and sensible guess.

I should add that the line of code I posted causes nullptr errors in a different class. Specifically, in an instance of a class that is, like ScoreTextPointer, a member variable of the class that the code is in.

So I'm not seeing anything UE4-specific (unless SetText() takes an FText instead of an FString and there's some weirdness with implicit conversions happening?) but my instinct for "another member of the same object is getting hosed up" is memory stomp.

Double Punctuation
Dec 30, 2009

Ships were made for sinking;
Whiskey made for drinking;
If we were made of cellophane
We'd all get stinking drunk much faster!
Isn’t that allocating an FString on the stack, meaning it’s eligible to be destroyed as soon as the method that made it goes out of scope?

Volte
Oct 4, 2004

woosh woosh
If it's affecting subsequent runs in the editor it must be doing something to the editor state that persists after the game is stopped, either clobbering it or stale data not being garbage collected properly would be my initial guesses. If there are static variables being used somewhere they shouldn't be I think that could also cause weirdness like that.

LLSix
Jan 20, 2010

The real power behind countless overlords

more falafel please posted:

So I'm not seeing anything UE4-specific (unless SetText() takes an FText instead of an FString and there's some weirdness with implicit conversions happening?) but my instinct for "another member of the same object is getting hosed up" is memory stomp.

Very well done. This is my best guess. Effectively a type error the compiler didn't catch and was resulting in it stomping all over memory.

The fix was

code:
ScoreTextPointer->SetText(FText::FromString(FString::FromInt(MAXIMUM_SCORE)));
Just making the conversion from an FString to an FText explicit resolved the issue.

Volte
Oct 4, 2004

woosh woosh
The documentation says that there's a deprecated FString overload as well as an FText overload, so it could be that the deprecated overload is just broken.

more falafel please
Feb 26, 2005

forums poster

This reminds me of the first game I ever worked on, which I believe had the first integration of Havok physics into UE3. We had Havok engineers onsite to do the integration (because I think Epic wouldn't let them have source themselves?) Anyway, the integration was wrought with problems (not Havok's fault, at least not entirely). Eventually we found a bug that was caused by initializing an hkArray with a function that returned bool. Except it wasn't bool, it was UBOOL, UE3's "great" "bool" type, which was typedef'd to explicitly signed int, for some loving reason. So since hkArray had a preallocating constructor that takes a capacity... everything compiles fine, except obviously nothing works because that's nothing.

Doc Block
Apr 15, 2003
Fun Shoe
Umm... what? Latest Visual Studio 2019 has an IntelliSense "warning" that this loop may read past the end of the array. This is uh, hmm :what:

C++ code:
for(unsigned int i = 0; i < numModes; ++i) {
	if(displayModeList[i].Width == (unsigned int)screenWidth) {
		if(displayModeList[i].Height == (unsigned int)screenHeight) {
			numerator = displayModeList[i].RefreshRate.Numerator;
			denominator = displayModeList[i].RefreshRate.Denominator;
		}
	}
}
numModes is initialized with a value fetched from the operating system (and the call is checked for failure), then displayModeList is created with numModes elements.

IntelliSense says this may read past the end of displayModeList:
Warning C6385: Reading invalid data from 'displayModeList': the readable size is 'numModes*28' bytes, but '56' bytes may be read.

I don't see how that's possible.

edit: it looks like it's assuming that i < numModes will always be true :rolleyes:


Yeah, of course if you assume that the loop condition is always true it'll eventually run past the end of the array, Microsoft!

edit 2: LOL @ it suggesting how Microsoft could improve their own API on the first line of that screenshot

edit 3: just gonna turn off IntelliSense warnings and call it good, because :stare:

Doc Block fucked around with this message at 23:58 on Mar 18, 2020

UraniumAnchor
May 21, 2006

Not a walrus.
I think what's actually going on there is that it's assuming displayModeList can only ever have one element? Because it's complaining that dML[1] might be invalid? It even says 'valid range 0 to 0'. How does it know how many elements dML has? There's probably a way to hint it.

Doc Block
Apr 15, 2003
Fun Shoe
Yeah but even if displayModeList only had one element, numModes would be 1 and so the for loop’s test would catch that.

It’s assuming that both displayModeList has one element (because ????), and that also the for loop test passes the second time through even though numModes would be 1 and it would actually fail.

In reality, displayModeList will have a dozen or more elements (all the currently attached monitor’s supported display modes), but I think even if I could hint this to IntelliSense, it’d still trip over assuming the for loop test always passes.

Doc Block
Apr 15, 2003
Fun Shoe
I should point out that this is just an IntelliSense warning; the actual compiler has no complaints, and the code works fine.

Just thought it was weird. Another nag warning added to VS 2019 for something that doesn’t need one (like how it complains about their own API using enum instead of enum class LOL)

nielsm
Jun 1, 2009



displayModeList is an array declared and allocated by the calling program. How is it doing that, operator new, malloc, something else?

Absurd Alhazred
Mar 27, 2010

by Athanatos
It's hilarious if Microsoft's IntelliSense freaks out over the Microsoft API pattern of "call with nullptr to get size, allocate, then call again with pointer and size to get values".

more falafel please
Feb 26, 2005

forums poster

Absurd Alhazred posted:

It's hilarious if Microsoft's IntelliSense freaks out over the Microsoft API pattern of "call with nullptr to get size, allocate, then call again with pointer and size to get values".

I mean, it should, in any sane world. That's an absolutely terrible paradigm.

Absurd Alhazred
Mar 27, 2010

by Athanatos

more falafel please posted:

I mean, it should, in any sane world. That's an absolutely terrible paradigm.

What would be your alternative that is usable and/or wrappable from C all the way up to C#? Should it be returning some kind of COM shared pointer to the data, that the user would then be required to deallocate?

Doc Block
Apr 15, 2003
Fun Shoe

nielsm posted:

displayModeList is an array declared and allocated by the calling program. How is it doing that, operator new, malloc, something else?

Like Absurd Alhazred said, by calling the function that would fill displayModeList with nullptr to find out how big it needs to be, then allocating it with new, then call the function to fill it again:

C++ code:
	HRESULT result;
	...
	// Get number of display modes that match desired output pixel format, then pick one that matches desired width and height
	unsigned int numModes = 0;
	DXGI_MODE_DESC *displayModeList = nullptr;

	// How many?
	result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, nullptr);
	if(FAILED(result)) {
		return false;
	}
	if(numModes == 0) {
		return false;
	}

	displayModeList = new DXGI_MODE_DESC[numModes];
	if(displayModeList == nullptr) {
		return false;
	}

	result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList);
	if(FAILED(result)) {
		return false;
	}

	// Within a given resolution, refresh rates are ordered from lowest to highest. Record the highest for our desired resolution.
	for(unsigned int i = 0; i < numModes; ++i) {
		if(displayModeList[i].Width == (unsigned int)screenWidth) {
			if(displayModeList[i].Height == (unsigned int)screenHeight) {
				numerator = displayModeList[i].RefreshRate.Numerator;
				denominator = displayModeList[i].RefreshRate.Denominator;
			}
		}
	}

Doc Block fucked around with this message at 18:46 on Mar 19, 2020

UraniumAnchor
May 21, 2006

Not a walrus.
I feel like there's got to be a better way to hint the checker to let it know what the size of the array is, but I forget how those annotations work.

Doc Block
Apr 15, 2003
Fun Shoe
There’s no way to know in advance how big it’s going to be. It’s every resolution supported by the currently attached monitor and GPU.

Knowing in advance how big it is won’t matter, since the checker seems to assume the loop test always passes.

nielsm
Jun 1, 2009



Okay no that's just the compiler being insufficiently smart. You make an allocation with operator new[] of a certain number of elements, the array isn't just going to change size for no reason.
Although, declare it as displayModeList[] instead of a pointer, maybe that makes it happier? Also no reason to declare it before assignment. I'm also pretty sure operator new does not return nullptr, either it succeeds or it throws std::bad_alloc.

Edit: No wait, yeah obviously numModes can change value between the calls, if you somehow manage to plug in a new monitor in the period between them. So yes the code could theoretically be wrong if an unlikely race condition occurs.

Absurd Alhazred
Mar 27, 2010

by Athanatos

nielsm posted:

Okay no that's just the compiler being insufficiently smart. You make an allocation with operator new[] of a certain number of elements, the array isn't just going to change size for no reason.
Although, declare it as displayModeList[] instead of a pointer, maybe that makes it happier? Also no reason to declare it before assignment. I'm also pretty sure operator new does not return nullptr, either it succeeds or it throws std::bad_alloc.

Edit: No wait, yeah obviously numModes can change value between the calls, if you somehow manage to plug in a new monitor in the period between them. So yes the code could theoretically be wrong if an unlikely race condition occurs.

I think that if that does happen, you will get the new value in numModes after the second call, I think, if it's smaller than in the previous call.

Also, I would recommend creating an std::vector instead of using a new allocation directly.

Doc Block
Apr 15, 2003
Fun Shoe
I'm trying to learn DirectX, and so this is just following some guy's (admittedly terrible) tutorial.

The code samples are terrible, with lots of stuff like
code:
void SomeClass::thing(whatever)
{
    declare every variable at the beginning of the function;
    now give some of them default values;
    doSomething();

    return;
}
I've been cleaning it up as I go along, but there's a whole lot of :stare: stuff in there.

Making displayModeList a vector instead of directly allocating it, with no other changes, made the IntelliSense warning go away, at least.

According to the docs, if the number of supported display modes for the current monitor were to somehow change between the first and second GetDisplayModeList() calls, the second call would fail with a DXGI_ERROR_MORE_DATA error, since it only uses numModes as an out parameter if you pass nullptr as the destination pointer. It won't change numModes if you give it a destination pointer, so it isn't possible for the for loop to overrun displayModeList.

Doc Block fucked around with this message at 20:57 on Mar 19, 2020

Absurd Alhazred
Mar 27, 2010

by Athanatos

Doc Block posted:

I'm trying to learn DirectX, and so this is just following some guy's (admittedly terrible) tutorial.

The code samples are terrible, with lots of stuff like
code:
void SomeClass::thing(whatever)
{
    declare every variable at the beginning of the function;
    now give some of them default values;
    doSomething();

    return;
}
I've been cleaning it up as I go along, but there's a whole lot of :stare: stuff in there.

Making displayModeList a vector instead of directly allocating it, with no other changes, made the IntelliSense warning go away, at least.

According to the docs, if the number of supported display modes for the current monitor were to somehow change between the first and second GetDisplayModeList() calls, the second call would fail with a DXGI_ERROR_MORE_DATA error, since it only uses numModes as an out parameter if you pass nullptr as the destination pointer. It won't change numModes if you give it a destination pointer, so it isn't possible for the for loop to overrun displayModeList.

Huh, fair enough.

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

Absurd Alhazred posted:

What would be your alternative that is usable and/or wrappable from C all the way up to C#? Should it be returning some kind of COM shared pointer to the data, that the user would then be required to deallocate?

You could pass an allocator function that the API impl can call with the right size before filling it in. Could even give it a sane default, I bet!

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

Subjunctive posted:

You could pass an allocator function that the API impl can call with the right size before filling it in. Could even give it a sane default, I bet!

Last (and only) time I programmed on Windows (almost a quarter century ago), I remember we ran into problems porting our application because memory allocated in a DLL couldn’t be freed elsewhere. Was this API maybe designed with such a restriction in mind?

Absurd Alhazred
Mar 27, 2010

by Athanatos

Zopotantor posted:

Last (and only) time I programmed on Windows (almost a quarter century ago), I remember we ran into problems porting our application because memory allocated in a DLL couldn’t be freed elsewhere. Was this API maybe designed with such a restriction in mind?

You could always go the Vulkan route and have an optional user-side allocation parameter.

Adbot
ADBOT LOVES YOU

nielsm
Jun 1, 2009



Zopotantor posted:

Last (and only) time I programmed on Windows (almost a quarter century ago), I remember we ran into problems porting our application because memory allocated in a DLL couldn’t be freed elsewhere. Was this API maybe designed with such a restriction in mind?

Yeah, Windows has always operated with the assumption that every program and shared library has its own copy of the C library. Two binaries in the same process won't necessarily be able to free memory allocated on the other side. So, if you want to function on Windows you need to either have callers allocate all memory, have callers supply an allocator interface, or export a free() function callers can use on objects you allocate for them.

(This is also why it's always puzzled me that Mingw went the route of linking in Microsoft's C runtime instead of using one of their own. Every other compiler and language on Windows does that, only Mingw decided the painful route of linking someone else's proprietary library.)

nielsm fucked around with this message at 23:36 on Mar 20, 2020

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply