News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Reliable way to enable misaligned exceptions?

Started by Fabioxds, December 02, 2017, 04:17:18 AM

Previous topic - Next topic

Fabioxds

Hi. :)

I would like to catch misaligned memory reads and/or writes while running my code on a x86 machine; does anyone know a safe and reliable way to do this? As far I know, the processor fixes them automatically, and the MSDN documentation states that the OS will never report them, even with the AC flag set, automatically fixing them instead.

Edit: Attached my source code

Basically, what I'm trying to do is:

I have a simple C module with a main function where a call my real procedure, written in assembly, wrapped in a __try __except block, so I don't have to deal with exceptions in assembly. The additional benefit is that any exception generated while my code is running is propagated back to main (assume my app is single threaded). I want to know if my assembly code is accessing any data on misaligned memory addresses, consequently raising EXCEPTION_DATATYPE_MISALIGNMENT exceptions. But the default behavior on x86 Windows is to ignore these and instead silently correct alignment faults even with the AC flag of EFLAGS enabled.

Here is my assembly:

      .486
.model flat,c
                .code

extern gMem:dword

_MyAsmEntryPoint proc ; WARNING: this is nasty code, learning purpose only!
        push ebp
        mov ebp,esp
        push ebx

pushfd ; this block sets the AC flag, but it doesnt matter, it will be reset after the xor instruction :(
pop eax
xor eax, 40000h
push eax
popfd

mov ebx, gMem    ; global memory allocated in main
xor ecx, ecx

@@: inc ebx
inc ecx              ; just too lazy to use the instructions with the rep prefix...
mov edx, [ebx]     ;these reads and writes don't raise any exceptions
mov [ebx], edx
cmp ecx, 32
jb @B

xor eax, eax
cdq
xor  ecx, ecx           
idiv ecx ; divide by 0 and catch it on main's exception handler

mov eax, 1 ; with the exception above, this should never execute

        pop ebx
        pop ebp
        ret
_MyAsmEntryPoint endp
        end

jj2007

#1
In theory, this should enable AC exception checks. In practice it seems to have no effect...
include \masm32\include\masm32rt.inc
.686p
.xmm
.data
db 0
MySingle REAL4 12345.67890
MyDouble REAL8 12345.67890

.code
start:
  pushf
  or dword ptr [esp], 1 shl 18   ; set AC flag
  popf
  maxss xmm0, MySingle
  movsd xmm1, MyDouble
  inkey "ok?"
  exit
end start

aw27

Quote from: Fabioxds on December 02, 2017, 04:17:18 AM
I would like to catch any misaligned exception while running any code on my x86 machine, does anyone know a safe and reliable way to do this?
There are a few interpretations of misalignment.
There is a sort of misalignment exception that the CPU automatically fixes unless you tell it not to do.
But from the way you put the question it appears that you don't also know how to catch exceptions in general.

hutch--

 :biggrin:

At the risk of sounding difficult, the only solution I know to fix mis-alignment is to not mis-align data in the first place. Your choice of instruction will address most of this, where you are using data from allocated memory, make sure it is aligned correctly for read and write locations.

Fabioxds

#4
Edit: updated original post

aw27

#5
I think that MSDN is right. Probably, you need to change the AM flag of CR0, in addition to the AC flag of EFAGS, which you can't do from ring 3.

However, it works on x64, except on some AMD. I tested it last month with someone from this forum that has a new Threadripper.

LiaoMi

With compiled example it would be easier to understand what is happening there. Сan try to use SetUnhandledExceptionFilter, you can set the filter and run without a debugger. 

    case EXCEPTION_DATATYPE_MISALIGNMENT:
        text = "Data Misalignment Exception";
        break;


or try another option

IsBadDwordPtr(
    LPDWORD p
    )
{
    //
    // Since IsBadWritePtr won't tell us if "p" is not DWORD-aligned (an
    // issue on non-x86 platforms), we use the following to determine
    // if the pointer is good.  Note that DWORD p points at will get
    // overwritten on successful completion of the request anyway, so
    // preserving the original value is not important.
    //

    DWORD dwError;


    try
    {
        *p = *p + 1;
    }
    except ((((dwError = GetExceptionCode()) == EXCEPTION_ACCESS_VIOLATION) ||
             dwError == EXCEPTION_DATATYPE_MISALIGNMENT) ?
            EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
    {
        return TRUE;
    }

    return FALSE;
}


IsBadReadPtr
IsBadCodePtr
IsBadStringPtr
IsBadWritePtr - Verifies that the calling process has read access to the specified range of memory.
https://msdn.microsoft.com/en-us/library/windows/desktop/aa366713(v=vs.85).aspx


IsBadReadPtr(
    CONST VOID *lp,
    UINT_PTR cb
    )

/*++

Routine Description:

    This function verifies that the range of memory specified by the
    input parameters can be read by the calling process.

    If the entire range of memory is accessible, then a value of FALSE
    is returned; otherwise, a value of TRUE is returned.

    Note that since Win32 is a pre-emptive multi-tasking environment,
    the results of this test are only meaningful if the other threads in
    the process do not manipulate the range of memory being tested by
    this call.  Even after a pointer validation, an application should
    use the structured exception handling capabilities present in the
    system to gaurd access through pointers that it does not control.

Arguments:

    lp - Supplies the base address of the memory that is to be checked
        for read access.

    cb - Supplies the length in bytes to be checked.

Return Value:

    TRUE - Some portion of the specified range of memory is not accessible
        for read access.

    FALSE - All pages within the specified range have been successfully
        read.

--*/

{

    PSZ EndAddress;
    PSZ StartAddress;
    ULONG PageSize;

    PageSize = BASE_SYSINFO.PageSize;

    //
    // If the structure has zero length, then do not probe the structure for
    // read accessibility or alignment.
    //

    if (cb != 0) {

        //
        // If it is a NULL pointer just return TRUE, they are always bad
        //
        if (lp == NULL) {
            return TRUE;
            }

        StartAddress = (PSZ)lp;

        //
        // Compute the ending address of the structure and probe for
        // read accessibility.
        //

        EndAddress = StartAddress + cb - 1;
        if ( EndAddress < StartAddress ) {
           return TRUE;
            }
        else {
            try {
                *(volatile CHAR *)StartAddress;
                StartAddress = (PCHAR)((ULONG_PTR)StartAddress & (~((LONG)PageSize - 1)));
                EndAddress = (PCHAR)((ULONG_PTR)EndAddress & (~((LONG)PageSize - 1)));
                while (StartAddress != EndAddress) {
                    StartAddress = StartAddress + PageSize;
                    *(volatile CHAR *)StartAddress;
                    }
                }
            except(EXCEPTION_EXECUTE_HANDLER) {
                return TRUE;
                }
            }
        }
    return FALSE;
}


Alignment Faults
The system alignment-fault handler is turned off by default on Itanium-based systems. Therefore, any unaligned data access generates an exception that will not automatically be fixed by the system unless the application catches the exception in a frame-based exception handler. To enable the system alignment-fault hander, call the SetErrorMode function with SEM_NOALIGNMENTFAULTEXCEPT. However, note that processes may experience severe performance degradation if the system alignment-fault handler is enabled and the process generates alignment faults.
If the WinDbg debugger has been installed as the system debugger, WinDbg will automatically be launched if any process on the system generates an unhandled exception. If you do not have a debugger installed as your system debugger, the system displays a dialog box stating that your application has encountered an error and providing the opportunity to report the problem to Microsoft.
On x64 systems, any alignment faults are handled by a combination of hardware and software. For best performance, all access to memory should be properly aligned.

Fabioxds

Hey LiaoMi, tnx for your suggestions. I'm still unable to catch those exceptions as you can see from my attached source code, it doesn't matter what we do, the processor will hapilly access odd adressess without a single complain. Unhandled exceptions seem to obey these rules too.
PS: Attached my source code on original post

LiaoMi

Quote from: Fabioxds on December 04, 2017, 02:42:11 AM
Hey LiaoMi, tnx for your suggestions. I'm still unable to catch those exceptions as you can see from my attached source code, it doesn't matter what we do, the processor will hapilly access odd adressess without a single complain. Unhandled exceptions seem to obey these rules too.
PS: Attached my source code on original post

Hi Fabioxds,
I cannt yet verify or compile, did you already try to change the rights with SetThreadErrorMode and SEM_NOALIGNMENTFAULTEXCEPT, as it says in msdn? The last option is to do everything manually and then call the function RaiseException.

                DWORD adw[2];
                PDWORD pdw = (void *)((char *)adw + 2);

                *pdw = (DWORD)-1;

                //
                // On some platforms the above will just work (hardware or
                // emulation), so we force it with RaiseException.
                //
                RaiseException((DWORD)EXCEPTION_DATATYPE_MISALIGNMENT,
                               0, 0, NULL);

nidud

#9
deleted

Fabioxds

LiaoMi, yep, I already tried messing with NOALIGNMENTFAULTEXCEPT flag but on x86 Windows it has no effects.

For anyone interested, I did find some interesting discussions about alignment issues on x86 hardware:

How to enable alignment exceptions for my process on x64?
https://stackoverflow.com/questions/26919269/how-to-enable-alignment-exceptions-for-my-process-on-x64

Disabling the program crash dialog
https://blogs.msdn.microsoft.com/oldnewthing/20040727-00/?p=38323

The last link has some interesting comments.

So, as aw7 pointed out earlier, the only way to enable unaligned exceptions is to mess with kernel-mode code and it seems unworthy all the trouble just to enable one kind of exception...

Then I will be doing these instead:

-Using asserts throughout my code (as nidud suggested)
-Ensuring proper aligned acesses depending on the data type at hand
-Careful debugging and some nights without sleeping  :badgrin:


felipe


jj2007

Quote from: Fabioxds on December 05, 2017, 11:43:38 AMThen I will be doing these instead:

-Using asserts throughout my code (as nidud suggested)
-Ensuring proper aligned acesses depending on the data type at hand
-Careful debugging and some nights without sleeping  :badgrin:

You could do that, of course, but why should you? Recent hardware has only a very small performance hit for misaligned access. What you get from HeapAlloc is align 8 anyway. SIMD code requires align 16 - sometimes, not always.

What is your specific problem with misalignment?

aw27

#13
As I thought, you can do it if you enable the AM flag of CR0.
I made a project and tested it with Windows 7 - 32 bit

I will not attach the project, but you can do it yourself.

1) Driver loader

Must be run as administrator:



#include <windows.h>
#include <stdio.h>


#define IOCTL_ENABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x902, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
#define IOCTL_DISABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x903, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)

extern "C"
{
void enableAC();
}

int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) {
printf("***Exception 0x%x at 0x%04Ix trapped in main***\n", code, reinterpret_cast<intptr_t>(ep->ExceptionRecord->ExceptionAddress));
return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
SC_HANDLE hSCManager;
SC_HANDLE hService;
SERVICE_STATUS ss;
HANDLE hFile;
DWORD dwReturn;
WCHAR dPath[MAX_PATH];
WCHAR fFile[MAX_PATH];

GetCurrentDirectory(MAX_PATH, dPath);
wsprintf(fFile, TEXT("%s\\%s"), dPath, L"testDrv32.sys");

printf("Loading Driver\n");
//getchar();
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);

if (hSCManager)
{
printf("Creating Service\n");

hService = CreateService(hSCManager, L"testDrv", L"testDrv Driver", SERVICE_START | DELETE | SERVICE_STOP, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, fFile, NULL, NULL, NULL, NULL, NULL);

if (!hService)
{
// Should not happen because service is deleted on exit
printf("Error Create service. Last error %d \r\n", GetLastError());
printf("Opening Service\n");
hService = OpenService(hSCManager, L"testDrv", SERVICE_START | DELETE | SERVICE_STOP);
}

if (hService)
{
printf("Starting Service\n");

if (StartService(hService, 0, NULL))
{
hFile = CreateFile(L"\\\\.\\testDrv", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

if (INVALID_HANDLE_VALUE != hFile)
{
printf("Enabling AM flag of CR0\n");
BOOL retVal=DeviceIoControl(hFile, IOCTL_ENABLE, NULL, 0,
NULL, 0, &dwReturn, NULL);
if (retVal)
{
printf("AM flag successully enabled. Enabling AC flag\n");
enableAC();
printf("Performing test\n");

__try
{
__asm
{
mov eax, [esp + 1] // alignment fault
}
}

__except (filter(GetExceptionCode(), GetExceptionInformation()))
{

printf("Executing Handler\n");

}

printf("Disabling AM flag of Cr0\n");

__asm
{
pushfd
btr dword ptr[esp], 12h // reset AC bit of eflags
popfd
}

DeviceIoControl(hFile, IOCTL_DISABLE, NULL, 0,
NULL, 0, &dwReturn, NULL);

}
else
printf("No data returned\n");
CloseHandle(hFile);
}
else
printf("No driver found\n");
}
else
printf("Error Starting service. Last error %d\n", GetLastError());

if (!ControlService(hService, SERVICE_CONTROL_STOP, &ss))
printf("Error ControlService. Last error %d\n", GetLastError());

if (!DeleteService(hService)) // This shall remove the Registry entries as well.
printf("Error DeleteService. Last error %d\n", GetLastError());

}
else
printf("Error Creating Service service. Last error %d\n", GetLastError());

CloseServiceHandle(hSCManager);
}
else
printf("Error \"%d\" opening Service Control Manager (are you running as Administrator?)\n", GetLastError());

Exit:
printf("Press <ENTER> to exit.");
getchar();
return 0;
}



ASM module (I could not make it work with inline ASM, not sure of the reason*). You must add the /safeseh switch in the file properties.

* Later I found the reason was optimization of release mode


.386
.model FLAT,C
.code

enableAC proc
pushfd
bts dword ptr[esp], 12h ; set AC bit of eflags
popfd
ret
enableAC endp

end


2- Driver files


#include <ntddk.h>
#include "main.h"

VOID testDrv_Unload(PDRIVER_OBJECT  DriverObject);
NTSTATUS DriverEntry(PDRIVER_OBJECT  pDriverObject, PUNICODE_STRING  pRegistryPath);

#pragma alloc_text(INIT, DriverEntry)
#pragma alloc_text(PAGE, testDrv_Unload)

NTSTATUS DriverEntry(PDRIVER_OBJECT  pDriverObject, PUNICODE_STRING  pRegistryPath)
{
UNREFERENCED_PARAMETER(pRegistryPath);

NTSTATUS NtStatus = STATUS_SUCCESS;
UINT uiIndex = 0;
PDEVICE_OBJECT pDeviceObject = NULL;
UNICODE_STRING usDriverName, usDosDeviceName;
DbgPrint("On DriverEntry");
RtlInitUnicodeString(&usDriverName, L"\\Device\\testDrv");
RtlInitUnicodeString(&usDosDeviceName, L"\\DosDevices\\testDrv");

NtStatus = IoCreateDevice(pDriverObject, 0, &usDriverName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject);

if (NtStatus == STATUS_SUCCESS)
{
for (uiIndex = 0; uiIndex < IRP_MJ_MAXIMUM_FUNCTION; uiIndex++)
pDriverObject->MajorFunction[uiIndex] = testDrv_UnSupportedFunction;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = testDrv_Create;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = testDrv_IoControl;

pDriverObject->DriverUnload = testDrv_Unload;

pDeviceObject->Flags &= (~DO_DEVICE_INITIALIZING);
IoCreateSymbolicLink(&usDosDeviceName, &usDriverName);
}
DbgPrint("DriverEntry Exit Status = '%d'", NtStatus);
return NtStatus;
}

VOID testDrv_Unload(PDRIVER_OBJECT  DriverObject)
{

UNICODE_STRING usDosDeviceName;

RtlInitUnicodeString(&usDosDeviceName, L"\\DosDevices\\testDrv");
IoDeleteSymbolicLink(&usDosDeviceName);

IoDeleteDevice(DriverObject->DeviceObject);
}



#include <ntddk.h>
#include <Ntstrsafe.h>
#include "main.h"

#pragma alloc_text(PAGE, testDrv_Create)
#pragma alloc_text(PAGE, testDrv_IoControl)
#pragma alloc_text(PAGE, testDrv_UnSupportedFunction)

NTSTATUS testDrv_UnSupportedFunction(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(Irp);
UNREFERENCED_PARAMETER(DeviceObject);

NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
return NtStatus;
}

NTSTATUS testDrv_IoControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);

PIO_STACK_LOCATION pIoStackIrp = NULL;

DbgPrint("testDrv_IoControl Called \r\n");

pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);

if (pIoStackIrp)
{
switch (pIoStackIrp->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_ENABLE:
   DbgPrint("testDrv_IoControl. Enabling AM flag of CR0 \r\n");
__asm
{
mov eax, cr0; //copy CR0 flags into EAX
or eax, 0x40000;
mov cr0, eax; //copy flags back
};

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);

return STATUS_SUCCESS;
case IOCTL_DISABLE:
DbgPrint("testDrv_IoControl. Diabling AM flag of CR0 \r\n");
__asm
{
mov eax, cr0; //copy CR0 flags into EAX
and eax, 0xFFFBFFFF;
mov cr0, eax; //copy flags back
};

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);

return STATUS_SUCCESS;

default:
DbgPrint("Unknown IOCTL code: %#x\n", pIoStackIrp->Parameters.DeviceIoControl.IoControlCode);

Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Information = 0;

IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_DEVICE_REQUEST;

}
}
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;

IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}


NTSTATUS testDrv_Create(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(Irp);
UNREFERENCED_PARAMETER(DeviceObject);
NTSTATUS NtStatus = STATUS_SUCCESS;
return NtStatus;
}


The output will be:

Loading Driver
Creating Service
Starting Service
Enabling AM flag of CR0
AM flag successully enabled. Enabling AC flag
Performing test
***Exception 0x80000002 at 0x13c11e1 trapped in main***
Executing Handler
Disabling AM flag of Cr0
Press <ENTER> to exit.

Fabioxds

jj2007, I just wanted my code to run faster by doing any optimizations I could think of.

Thanks to aw27 now we have a working solution  :bgrin: as I was too lazy to do it. It works  :eusa_clap: