bug in mmap() and mremap() return value - and a fix

Massimiliano Ghilardi massimiliano.ghilardi at gmail.com
Thu Jan 9 00:15:20 UTC 2014


On 01/06/14 23:25, Luís Oliveira wrote:
> On Mon, Jan 6, 2014 at 9:06 PM, Massimiliano Ghilardi
> <massimiliano.ghilardi at gmail.com> wrote:
>> In such cases, it means the underlying C mmap() function returned
>> successfully a pointer in the top half of the addressable virtual
>> memory, but the Lisp wrapper for mmap() and mremap() chokes on such
>> pointers.
>>
>> A patch that solves this problem is attached.
>>
>> Feedback on the analysis and the solution is welcome :)
>
> First off, there's another bug: we should be using :intptr rather than
> :long for the return type. To be fair, CFFI probably didn't have that
> type yet when that code was written.
>

I did not know about :intptr. From the cffi/src/types.lisp sources
it turns out to be "a signed int as wide as :pointer",
so either :intptr or :uintptr are good for this case.

Unluckily, converting a negative :intptr to a :pointer still requires 
some trickery. My solution was
   (cffi-sys:make-pointer (logand address +most-positive-pointer+)))
but it has the annoyance of computing +most-positive-pointer+ correctly.

On the other hand, your solution

> The return-filter can then cast the return value to a pointer using
> something like:
>
>    (with-foreign-object (p :intptr)
>      (setf (mem-ref p :intptr) <the-return-value>)
>      (mem-ref p :pointer))

for each call it allocates (or seems to allocate) memory for the
foreign-object P, writes an :intptr to it, only to read it back
as a :pointer - a type-punning that even some C programmers frown upon.

> Alternatively, we could store a -1 cast to pointer and compare the
> return value against that. (Hmm, maybe CFFI should have a cast
> operation. :-))

I was rather thinking about defining some constant as
    ((void *)-1)
(which is actually the definition of MAP_FAILED, at least on some
systems) and grovel it, but groveled constants can only be integers :(

So your suggestion to have a Lisp constant "map-failed cast to pointer" 
sounds nice.

This raises another interesting point: at least on my system,
64-bit Lisps (SBCL, CCL) grovel
   (cl:defconstant map-failed 18446744073709551615 "mmap: failure")
while 32-bit ones (CMUCL, CCL) grovel
   (cl:defconstant map-failed -1 "mmap: failure")

Quite annoying.

>
> I'm not sure any of these alternatives are better than your solution.
> What do you think?
>

The situation is much more "dirty" than I thought initially:
1) on Win64, "long" is smaller than "intptr" - see
    http://permalink.gmane.org/gmane.lisp.cffi.devel/2112

2) groveled map-failed sometimes is -1 and sometimes is #xFFFF...FFFF

Also, I'd rather minimize consing and thus call (with-foreign-object 
...) sparingly. For the same reason, I'd rather minimize the use of 
:uintptr, which can exceed fixnum and thus allocate memory.

My opinion is:

1) using *signed* integers (of any size) to represent pointers is asking
    for trouble. Also, cffi-sys:make-pointer does not accept them.
    So I strongly prefer to use :uintptr as mmap/mremap return type.

2) Thinking that (or actually defining) MAP_FAILED = -1 is misleading.
    MAP_FAILED is (void *)-1.

3) There is no predefined constant for "the maximum value of a
    :pointer" or more exactly "the maximum value of an :uintptr".
    An accurate definition requires groveling CHAR_BIT from <limits.h>
    and then defining
    (defconstant +most-positive-uintptr+
       (1- (ash 1 (* char-bit (cffi:foreign-type-size :uintptr)))))

    alternatively, the type-punning trick you suggested can be used
    as well
    (defconstant +most-positive-uintptr+ (with-foreign-object (p :intptr)
       (setf (mem-ref p :intptr) -1)
       (mem-ref p :uintptr)))

    I'd go for this second option, as it avoids groveling CHAR_BIT.

4) once we have +most-positive-uintptr+, we can easily work around
    the fact that sometimes groveled map-failed is positive and
    sometimes not.

In conclusion, I like your ideas and I updated the patch by integrating
most of them.

-------------- next part --------------
diff --git a/posix/early.lisp b/posix/early.lisp
index 88167d7..bf7d5d1 100644
--- a/posix/early.lisp
+++ b/posix/early.lisp
@@ -119,6 +119,13 @@
    (object :initarg :object :reader errno-object)
    (function-name :initarg :function-name :reader function-name)))
 
+;; NOTE: this is an ugly type-punning. An alternative is to compute
+;; this value from (cffi:foreign-type-size :uintptr), but it requires
+;; also knowing how many bits are in a byte.
+(defconstant +most-positive-uintptr+ (with-foreign-object (p :intptr)
+                                       (setf (mem-ref p :intptr) -1)
+                                       (mem-ref p :uintptr)))
+
 ;;; FIXME: undocumented in cffi-grovel.
 (defun make-from-pointer-function-name (type-name)
   (format-symbol t "~A-~A-~A-~A" '#:make type-name '#:from '#:pointer))
diff --git a/posix/unix.lisp b/posix/unix.lisp
index f65ff55..7ddcb2c 100644
--- a/posix/unix.lisp
+++ b/posix/unix.lisp
@@ -308,6 +308,9 @@
 
 ;;;; sys/mman.h
 
+;; on some systems, map-failed is groveled as -1 :(
+(defvar *map-failed-pointer* (make-pointer (logand map-failed +most-positive-uintptr+)))
+
 (defsyscall "mlock" :int
   "Lock a range of process address space."
   (addr :pointer)
diff --git a/posix/wrappers.lisp b/posix/wrappers.lisp
index ae49ad8..dfc2a15 100644
--- a/posix/wrappers.lisp
+++ b/posix/wrappers.lisp
@@ -60,9 +60,8 @@
 
 #-windows
 (defwrapper "mmap" ("void*" (errno-wrapper
-                             :long
-                             :error-predicate (lambda (p) (= p map-failed))
-                             :return-filter make-pointer))
+                             :pointer
+                             :error-predicate (lambda (p) (pointer-eq p *map-failed-pointer*))))
   (start :pointer)
   (length ("size_t" size))
   (prot :int)
@@ -72,9 +71,8 @@
 
 #+linux
 (defwrapper "mremap" ("void*" (errno-wrapper
-                               :long
-                               :error-predicate (lambda (p) (= p map-failed))
-                               :return-filter make-pointer))
+                               :pointer
+                               :error-predicate (lambda (p) (pointer-eq p *map-failed-pointer*))))
   (old-address :pointer)
   (old-size ("size_t" size))
   (new-size ("size_t" size))


More information about the osicat-devel mailing list