New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Debug mode: enforce lifetime of char pointers returned from the API #259
Debug mode: enforce lifetime of char pointers returned from the API #259
Conversation
de53f21
to
5b89537
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a brilliant idea to me 💯
I think it deserves some better documentation though. I don't think we have user-facing docs for the debug mode yet, but for sure you should add some lenghty comment at the start of _debug_internal.h
which explains the general idea of this check and how the various get_protected_raw_data_size
& co. works, how/when the raw memory is allocated and freed, etc., because it's a bit hard to follow the logic by just reading the code.
Also, random idea for the future: I wonder whether we can write an implementation of raw_data_*
which somehow integrates with AddressSanitizer and/or whether it would have any advantage over the current mmap-based implementation.
// pointer to and size of any raw data associated with | ||
// the lifetime of the handle: | ||
void *associated_data; | ||
HPy_ssize_t associated_data_size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks certainly good for this specific PR.
However, we are wasting some space even for handles which don't require these fields (which are most of them).
For the future, we might want to design a sort of "DebugHandle
subclassing" to make it possible to create different kinds of handles with different kinds of extra data, maybe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was concerned about this too. For now I don't see any other significantly better solution. I think the subclassing would not play well with the reusing of closed handles? In the future, in case we need to attach even more data to the debug handles, maybe we can use indirection with just one field in DebugHandle
pointing to DebugHandleData
or such and that would be allocated only if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, if we do "subclassing" we would need to maintain a separate queue of closed handle for each different kind (or at least for each different sizeof()).
DebugHandleData
works in theory but I don't know how good it is in practice, because you pay the cost of the pointer + the hidden space cost of an extra malloc (but I don't know how much they are).
Another idea to keep into consideration is to use a union
if/when we will need more kind of data.
} | ||
return new_ptr; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you addressed this concern in another comment but I wanted to double check. What happens if you mmap()
lots of small buffers? The returned regions are aligned to page boundary (so, 4096 in general), but I assume that this consumes only the virtual address space and not the physical memory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think your concern is right. I think you cannot get smaller amount of physical memory than a page? I did not think about that.
We can introduce separate max number of the buffers alongside the max total size. On the other hand, the number of the buffers is limited by the max number of handles, so it should be fine I believe. The defaults are:
static const HPy_ssize_t DEFAULT_CLOSED_HANDLES_QUEUE_MAX_SIZE = 1024;
static const HPy_ssize_t DEFAULT_PROTECTED_RAW_DATA_MAX_SIZE = 1024 * 1024 * 10;
if I see it correctly, currently the total size limit (10MB) is larger than if every of the 1024 handles had a tiny buffer attached (4MB for 4KB pages).
Moreover, thinking about this more: we always create the "debugging" copy of the data. The limits apply only to data attached to closed handles. This may mean that debug mode run may run out of memory when normal run wouldn't, but that may happen for other reasons too, so not sure if this is a big concern. The necessary amount of memory is "just" 2x, i.e., grows linearly, so I think it's fine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you cannot get smaller amount of physical memory than a page?
indeed, I think you are correct.
On the other hand, the number of the buffers is limited by the max number of handles, so it should be fine I believe [cut]
yes, this looks correct as well. I vote for including your computation/explanation in the "big docstring which explains how it works".
This may mean that debug mode run may run out of memory when normal run wouldn't, but that may happen for other reasons too, so not sure if this is a big concern. The necessary amount of memory is "just" 2x, i.e., grows linearly, so I think it's fine?
yes, I think it's fine if debug mode requires more memory. Maybe at some point we can think of introducing optional checks (like, I want to enable leak checks but not raw buffers to save memory or so) but I don't think it's too important now.
val = (val + 1) % 10; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether we could write something which is more readable in a debugger, like 0xDEADBEEF
or "THIS MEMORY WAS FREED"
. But it's also true that this implementation will almost never be used in practice as linux is covered by the other one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I went for 0xbad0da7a
, because we want the data to be invalid when decoded. In the tests of this feature (test_charptr.py
), when mmap is not available, we're now checking that the program fails with UnicodeDecodeError
.
Right, the advantage would be that instead of just failing, we'd get a trace and all the info that AddressSanitizer provides. That would be cool. I could not find any API that AddressSanitizer would provide though. We would need to be able to mark some memory as "read only" and then as "invalid". In general providing stack trace and some additional info would be useful for other debugging features like the invalid handle detection. |
yes but now that I think of it, this would work only if the user compiles its own code with ASan. In that case, it's probably enough to just
yes, that's also something which I wanted to add (and that should be optional since it's probably costly). Each |
Last time I checked getting a stack trace in C in at least so-so portable way was not a simple thing to do. There are some libraries providing that functionality. What do you think about bringing some 3rd party dependency for that? We can probably make it at least optional with |
yes, it's not portable and doesn't always work but that's life. |
Adds two fields to DebugHandle struct to keep any raw data associated with that handle and the size of the data. When the handle is closed, the associated data memory region is mprotected from reading/writing. AsUTF8AndSize returns a copy of the original result and saves that to the DebugHandle. The copy is also mprotected from reading.
Use _HPY_DEBUG_MEM_PROTECT_USEMMAP instead of _HPY_POSIX. Additionally allow to unconditionally disable _HPY_DEBUG_MEM_PROTECT_USEMMAP by exporting _HPY_DEBUG_FORCE_DEFAULT_MEM_PROTECT when building HPy.
… variable warning
5b89537
to
5805b1c
Compare
this is done now |
5e8e5a7
to
40e9278
Compare
@antocuni I believe I addressed all your comments. If you want, please, re-review the PR or let me know that you'd like to do so later. Otherwise I'd merge this next week. |
Implementation of #236.
This PR is built on top of #254. It probably makes sense to first review & merge that PR, or look at the diff the two PRs (available here: steve-s#1)