GetLastError() (with and without debugger)
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Tue Aug 16 15:22:26 UTC 2022
Python C types [1] seems to go down a similar path:
> The /use_last_error/ parameter, when set to true, enables the same
> mechanism for the Windows error code which is managed by the
> |GetLastError()|
> <https://docs.python.org/3/library/ctypes.html#ctypes.GetLastError>
> and |SetLastError()| Windows API functions; |ctypes.get_last_error()|
> <https://docs.python.org/3/library/ctypes.html#ctypes.get_last_error>
> and |ctypes.set_last_error()|
> <https://docs.python.org/3/library/ctypes.html#ctypes.set_last_error>
> are used to request and change the ctypes private copy of the windows
> error code.
We currently have an enhancement open to add various kind of extra
linkage information [2]. I believe this is yet another case where custom
linkage information is required. I have updated that JBS entry to
capture some info in this thread.
Maurizio
[1] - https://docs.python.org/3/library/ctypes.html
[2] - https://bugs.openjdk.org/browse/JDK-8292047
On 16/08/2022 15:29, Manuel Bleichenbacher wrote:
> Another option is to make it part of the function descriptor / calling
> convention, similar to how .NET Interop does it:
>
> [DllImportAttribute("user32.dll", SetLastError = true, CharSet =
> CharSet.Unicode)] public static extern int MessageBox(IntPtr hwnd,
> String text, String caption, uint type);
>
> With the SetLastError annotation, this function call will save the
> last error in a place where the .NET runtime environment will not
> overwrite it. It can then be queried at anytime using
> Marshal.GetLastWin32Error(), or rather at anytime until calling the
> next Interop function. The method call must be atomic of course.
>
> A similar implementation would be needed for errno on Linux.
>
>
> On Tue, Aug 16, 2022 at 3:55 PM Thomas Stüfe <thomas.stuefe at gmail.com>
> wrote:
>
>
> A pragmatic and low-tech way to solve this could be to build a
> library that wraps all (or a subset of) win32 APIs and just
> reworks them slightly to return the error (GetLastError or
> WSAGetLastError) as part of the call. E.g. by prepending or
> appending a (DWORD* errorcode) parameter. If we do this and ship
> this utility wrapper library as part of the JDK, we can probably
> help 99% of callers who have this problem.
>
> ..Thomas
>
> On Tue, Aug 16, 2022 at 1:26 PM Maurizio Cimadamore
> <maurizio.cimadamore at oracle.com> wrote:
>
>
> On 15/08/2022 21:49, Manuel Bleichenbacher wrote:
>> I've run a test with jdb, and the outcome is the same: the
>> program behaves incorrectly.
>>
>> Do you guys know if this is caused by the debugger only, or
>> if it is a general problem that is more likely to occur with
>> the debugger but could also occur if run without debugger?
>
> Hi,
> as David pointed out, nothing in the JVM, debuggers, agents
> etc. takes this use case into account - which means that
> whether errno/LastError is preserved is, ultimately,
> platform/OS-dependent.
>
> In JNI the status quo has probably been good enough: after all
> with JNI you can always insert your errno/LastError check
> directly into the native code, ensuring atomicity.
>
> But when you use some kind of FFI, each Java call corresponds
> to _single_ native call - which means that, even if you want
> to do:
>
> <call function>
> <getLastError>
>
> In reality there are some operations which "might" happen
> between the two calls (after all, the JVM is a complex piece
> of software, the garbage collector might need to allocate more
> memory, etc.).
>
> This explains why some of the frameworks out there shadow (and
> save) errno/LastError into separate thread-local storage, so
> that it can be read by other FFI calls. What I've seen doesn't
> seem a particularly compelling solution though, because: (a)
> the FFI support has to add platform-dependent logic to mimic
> what e.g. getLastError and setLastError would do, and (b) the
> FFI support needs to "rewire" calls to such native functions,
> so that they return the shadowed storage instead. So you could
> end up with cases where the native LastError value and the FFI
> value are out of sync.
>
> A more general solution would be to combine multiple downcall
> method handles into one, so that e.g. one could apply a
> GetLastError filter to a downcall method handle, which calls
> GetLastError after the native call, and saves the value into
> some user-defined variable, and does that within the _same_
> native execution as the original call (e.g. only one
> Java->native transition, covering both calls). But the Linker
> API does not currently offer such capability (and something
> like that would be a bit on the boundary of what a method
> handle combinator API is allowed to do).
>
> Maurizio
>
>
>>
>> On Fri, Aug 12, 2022 at 3:16 PM Maurizio Cimadamore
>> <maurizio.cimadamore at oracle.com> wrote:
>>
>> Hi Manuel,
>> thanks for submitting this issue.
>>
>> I think your hunch is probably correct - something inside
>> the JDK is resetting the value of LastError.
>>
>> In Hotspot code I see some abstractions to preserve the
>> LastError value (os_windows.cpp):
>>
>> // A number of wrappers for more frequently used system
>> calls, to add standard logging. struct PreserveLastError {
>> const DWORD v;
>> PreserveLastError() : v(::GetLastError()) {}
>> ~PreserveLastError() { ::SetLastError(v); }
>> };
>>
>>
>> And this is used in a number of OS-specific function
>> calls, to avoid polluting the last error value.
>>
>> That said, when you are running with a debugger,
>> especially inside an IDE (which might add its own hooks),
>> I think most bets are off, as the debugger might indeed
>> perform additional native calls which might not preserve
>> lastError correctly.
>>
>> One experiment would be to try and debugging using jdb -
>> so that at least we'd rule the IDE out, and see if the
>> issue is still there. If that's the case we'll try to
>> reach out to somebody more intimate with architetcure of
>> JPDA, to see if that's something that can be addressed
>> (perhaps in a way similar to what hotspot code seems to
>> be already doing).
>>
>> Thanks
>> Maurizio
>>
>>
>>
>>
>> On 12/08/2022 10:21, Manuel Bleichenbacher wrote:
>>> Thanks for the work on project Panama. It's an exciting
>>> technology. I'm using it to make native operating system
>>> services available to Java.
>>>
>>> On Windows I've run into an issue. This C/C++ Windows code:
>>>
>>> BOOL res = WriteFile(INVALID_HANDLE_VALUE, NULL, 0,
>>> NULL, NULL);
>>> DWORD err = GetLastError();
>>> printf("WriteFile result: %d, GetLastError result:
>>> %d\n", res, err);
>>>
>>> prints (as expected):
>>>
>>> WriteFile result: 0, GetLastError result: 6
>>>
>>> 6 is the value of the constant ERROR_INVALID_HANDLE.
>>>
>>> Using panama, I've translated the code to Java. The
>>> result is:
>>>
>>> Without debugger, the output is the same. Everything is ok.
>>>
>>> With the debugger, the result is incorrect:
>>>
>>> WriteFile result: 0, GetLastError result: 0
>>>
>>> This is incorrect. WriteFile() indicates an error, but
>>> GetLastError() returns 0 (= NO_ERROR). Could it be that
>>> the debugger calls another Windows API function between
>>> those two functions, resetting the last error?
>>>
>>> In my project that's a major issue. Since the software
>>> behaves incorrectly with the debugger, the software can
>>> no longer be debugged. This doesn't just affect it when
>>> debugging this particular piece of code but anytime this
>>> code is run in a debugging session.
>>>
>>> Is there something I'm not doing incorrectly? Or is
>>> there a fix or workaround?
>>>
>>> Here's the Java code:
>>>
>>> import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import static java.lang.foreign.MemoryAddress.NULL; import static java.lang.foreign.ValueLayout.ADDRESS; import static java.lang.foreign.ValueLayout.JAVA_INT; public class WinApi {
>>> static final MethodHandleWriteFile$Func; static final MethodHandleGetLastError$Func; static final MemoryAddressINVALID_HANDLE_VALUE = MemoryAddress.ofLong(-1); static {
>>> var linker = Linker.nativeLinker(); var lookup = SymbolLookup.libraryLookup("Kernel32", MemorySession.global()); WriteFile$Func = linker.downcallHandle(
>>> lookup.lookup("WriteFile").get(), FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS, JAVA_INT, ADDRESS, ADDRESS)
>>> ); GetLastError$Func = linker.downcallHandle(
>>> lookup.lookup("GetLastError").get(), FunctionDescriptor.of(JAVA_INT)
>>> ); }
>>>
>>> public static void main(String[] args) {
>>> var res =WriteFile(INVALID_HANDLE_VALUE, NULL, 0, NULL, NULL); var err =GetLastError(); System.out.printf("WriteFile result: %d, GetLastError result: %d\n", res, err); }
>>>
>>> static int WriteFile(MemoryAddress hFile, MemoryAddress lpBuffer, int nNumberOfBytesToWrite, MemoryAddress lpNumberOfBytesWritten, MemoryAddress lpOverlapped) {
>>> try {
>>> return (int)WriteFile$Func.invokeExact((Addressable)hFile, (Addressable)lpBuffer, nNumberOfBytesToWrite, (Addressable)lpNumberOfBytesWritten, (Addressable)lpOverlapped); }catch (Throwable e) {
>>> throw new RuntimeException(e); }
>>> }
>>>
>>> static int GetLastError() {
>>> try {
>>> return (int)GetLastError$Func.invokeExact(); }catch (Throwable e) {
>>> throw new RuntimeException(e); }
>>> }
>>> }
>>>
>>> Cheers
>>> Manuel
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/panama-dev/attachments/20220816/1d448da3/attachment-0001.htm>
More information about the panama-dev
mailing list