@@ -113,8 +113,9 @@ namespace detail {
113113 return perms;
114114 }
115115 #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
116+ #if !defined(__OpenBSD__)
116117 // Fetch VM mappings via sysctl with retry on ENOMEM.
117- // Freebsd uses a len * 4/3 heuristic in kinfo_getvmmap, we try to be more robust by retrying.
118+ // FreeBSD uses a len * 4/3 heuristic in kinfo_getvmmap, we try to be more robust by retrying.
118119 // On all BSD I have investigated, sysctl returns ENOMEM when the buffer is too small (mappings grew between the
119120 // size query and the data fetch). When this happens, oldlenp gives the amount copied, not the amount needed, so we
120121 // must re-query the size from scratch.
@@ -128,7 +129,6 @@ namespace detail {
128129 auto original_len = len;
129130 // https://github.com/lattera/freebsd/blob/401a161083850a9a4ce916f37520c084cff1543b/lib/libutil/kinfo_getvmmap.c#L32C2-L32C20
130131 len = len * 4 / 3 ;
131- // OpenBSD requires the buffer size to be a multiple of sizeof(kinfo_vmentry)
132132 len -= len % sizeof (struct kinfo_vmentry );
133133 len = std::max (len, original_len);
134134 std::vector<char > buf (len);
@@ -142,6 +142,7 @@ namespace detail {
142142 }
143143 throw internal_error (" sysctl vmmap failed after {} retries due to growing memory mappings" , max_retries);
144144 }
145+ #endif
145146 #if defined(__FreeBSD__)
146147 int get_page_protections (void * page) {
147148 int mib[4 ] = {CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, getpid ()};
@@ -191,20 +192,44 @@ namespace detail {
191192 );
192193 }
193194 #elif defined(__OpenBSD__)
195+ // OpenBSD's KERN_PROC_VMMAP returns at most VMMAP_MAXLEN (64KB) per call and rejects larger buffers with EINVAL.
196+ // The API is paginated, setting kve_start in the first buffer entry tells the kernel where to resume.
194197 int get_page_protections (void * page) {
195198 int mib[3 ] = {CTL_KERN, KERN_PROC_VMMAP, getpid ()};
196- auto buf = sysctl_vmmap (mib, 3 );
199+ size_t buf_size = 0 ;
200+ if (sysctl (mib, 3 , nullptr , &buf_size, nullptr , 0 ) != 0 ) {
201+ throw internal_error (" sysctl vmmap size query failed: {}" , strerror (errno));
202+ }
203+ buf_size -= buf_size % sizeof (struct kinfo_vmentry );
197204 auto addr = reinterpret_cast <uintptr_t >(page);
198- auto count = buf.size () / sizeof (struct kinfo_vmentry );
199- auto * entries = reinterpret_cast <struct kinfo_vmentry *>(buf.data ());
200- for (size_t i = 0 ; i < count; i++) {
201- if (addr >= entries[i].kve_start && addr < entries[i].kve_end ) {
202- int perms = 0 ;
203- if (entries[i].kve_protection & KVE_PROT_READ) perms |= PROT_READ;
204- if (entries[i].kve_protection & KVE_PROT_WRITE) perms |= PROT_WRITE;
205- if (entries[i].kve_protection & KVE_PROT_EXEC) perms |= PROT_EXEC;
206- return perms;
205+ std::vector<char > buf (buf_size);
206+ unsigned long next_start = 0 ;
207+ while (true ) {
208+ reinterpret_cast <struct kinfo_vmentry *>(buf.data ())->kve_start = next_start;
209+ size_t len = buf_size;
210+ if (sysctl (mib, 3 , buf.data (), &len, nullptr , 0 ) != 0 ) {
211+ throw internal_error (" sysctl vmmap failed: {}" , strerror (errno));
212+ }
213+ if (len == 0 ) {
214+ break ;
215+ }
216+ auto count = len / sizeof (struct kinfo_vmentry );
217+ auto * entries = reinterpret_cast <struct kinfo_vmentry *>(buf.data ());
218+ for (size_t i = 0 ; i < count; i++) {
219+ if (addr >= entries[i].kve_start && addr < entries[i].kve_end ) {
220+ int perms = 0 ;
221+ if (entries[i].kve_protection & KVE_PROT_READ) perms |= PROT_READ;
222+ if (entries[i].kve_protection & KVE_PROT_WRITE) perms |= PROT_WRITE;
223+ if (entries[i].kve_protection & KVE_PROT_EXEC) perms |= PROT_EXEC;
224+ return perms;
225+ }
226+ }
227+ if (len < buf_size) {
228+ break ;
207229 }
230+ // basic sanity check for forward progress
231+ VERIFY (next_start == 0 || entries[count - 1 ].kve_end > next_start);
232+ next_start = entries[count - 1 ].kve_end ;
208233 }
209234 throw internal_error (
210235 " Failed to find mapping for {>16:0h} via sysctl KERN_PROC_VMMAP" ,
0 commit comments