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