Dart FFI: managing natively allocated memory

1,543 views
Skip to first unread message

Maarten Boekhold

unread,
Dec 9, 2021, 12:33:32 AM12/9/21
to Dart Misc

Hi all,

I’ve waited a while with posting this because recently I was twice too hasty with posting, and realized my mistakes soon after those posts. However I’ve been staring at this one for 3 days now, and it is bugging me so much that it is starting to affect work.

I’m trying to create Dart FFI bindings for the SANE library, and I have an issue around the binding of one native function sane_get_devices. I can get the call to work, but when exiting my test program, the Dart VM crashes wit a SEGFAULT, without providing a stack trace. This probably means that I’m doing something wrong with the memory allocation.

As a test, I am trying to replicate the functionality of the following small C program:

listdevs.c

#include <stdio.h>
#include <sane/sane.h>

int main(int argc, char **argv) {
    const SANE_Device **deviceList;

    int vc;
    SANE_Status status;

    status = sane_init(&vc, NULL);
    status = sane_get_devices(&deviceList, 0);
    for (int i = 0 ; deviceList[i] != NULL ; i++) {
      printf("%s\n", deviceList[i]->name);
    }
    sane_exit();
}

Compile with gcc listdev.c -o listdevs -lsane. The memory behind deviceList is allocated by sane_get_devices inside the native C code, and freed inside the C code by sane_exit.

My ‘equivalent’ Dart code is:

pubspec.yaml

name: sane
description: A library to access the SANE native library
version: 1.0.0

publish_to: none

environment:
  sdk: '>=2.14.4 <3.0.0'

dependencies:
  ffi: ^1.0.0

listdevs.dart

import 'dart:ffi';
import 'package:ffi/ffi.dart';

late int Function(
    Pointer<Int32> version,
    Pointer<Void> authCallBack
    ) sane_init;
typedef sane_init_native_t = Int32 Function(
    Pointer<Int32> version,
    Pointer<Void> authCallBack
  );

late void Function() sane_exit;
typedef sane_exit_native_t = Void Function();

late int Function(
    Pointer<Pointer<Pointer<SANE_Device>>> device_list,
    int local_only
    ) sane_get_devices;
typedef sane_get_devices_native_t = Int32 Function(
    Pointer<Pointer<Pointer<SANE_Device>>> devices,
    Int32 local_only
  );

class SANE_Device extends Struct {
  external Pointer<Utf8> name;
  external Pointer<Utf8> vendor;
  external Pointer<Utf8> model;
  external Pointer<Utf8> type;
}

void main() {
  // Boiler-plate for the bindings
  late DynamicLibrary sane;
  sane = DynamicLibrary.open('libsane.so');

  sane_init = sane
      .lookup<NativeFunction<sane_init_native_t>>('sane_init')
      .asFunction();
  sane_exit = sane
      .lookup<NativeFunction<sane_exit_native_t>>('sane_exit')
      .asFunction();
  sane_get_devices = sane
    .lookup<NativeFunction<sane_get_devices_native_t>>('sane_get_devices')
    .asFunction();

  // Test code, error handling omitted
  Pointer<Int32> vc = calloc();
  int res = sane_init(vc, nullptr);
  calloc.free(vc);

  // ***SANE_Device
  Pointer<Pointer<Pointer<SANE_Device>>> device_list = calloc();
  res = sane_get_devices(device_list, 0);

  for (int i = 0 ; device_list.value[i] != nullptr ; i++) {
    print(device_list.value[i].ref.name.toDartString());
  }
  sane_exit();
  calloc.free(device_list);
}
  • dart pub get
  • dart listdevs.dart

This runs, but crashes with:

===== CRASH =====
si_signo=Segmentation fault(11), si_code=1, si_addr=0x7f5c45813160
version=2.14.4 (stable) (Wed Oct 13 11:11:32 2021 +0200) on "linux_x64"
pid=838074, thread=838082, isolate_group=(nil)((nil)), isolate=(nil)((nil))
isolate_instructions=0, vm_instructions=56338d220ec0
Stack dump aborted because GetAndValidateThreadStackBounds failed.
/home/boekhold/local/flutter/bin/internal/shared.sh: line 225: 838074 Aborted                 (core dumped) "$DART" "$@"
  • If I comment out the call to sane_get_devices() there is no crash
  • If I comment out the call to sane_exit() there is no crash

I am sure this has something to do with memory management, and probably with how I declare/use Pointer<Pointer<Pointer<SANE_Device>>> device_list, but I just can’t see it.

Links to source code of the SANE library:

I have also opened an issue about this on the sane-project in GitLab about this, to try to get a perspective from the other side: https://gitlab.com/sane-project/frontends/-/issues/27

I would really appreciates some pointers (no pun intended!) on how to properly handle the call to sane_get_devices and avoid this SEGFAULT!

Maarten Boekhold

unread,
Dec 9, 2021, 2:08:32 AM12/9/21
to Dart Misc, Maarten Boekhold

Following up on myself, I believe I’m using the same ‘pattern’ as the SQLite 3 bindings from the sqlite example referenced from the Dart C-Interop page:

    Pointer<Pointer<types.Database>> dbOut = calloc();
    final pathC = path.toNativeUtf8();
    final int resultCode =
        bindings.sqlite3_open_v2(pathC, dbOut, flags, nullptr);
    _database = dbOut.value;
    calloc.free(dbOut);

For my case I need a ‘3-level pointer’ because the object ‘returned’ is an array, in the case of the SQLite Database object it’s a single object, so there is only a '2-level' pointer.

Can my crash be caused by a bug or design issue in libsane itself?

Daco Harkes

unread,
Dec 9, 2021, 4:44:07 AM12/9/21
to mi...@dartlang.org, Maarten Boekhold
Hi Maarten,

The only difference I see between your C and Dart code is the life-time of vc.
In the C code vc stays alive till the end of the block, which is after the sane_exit() call.
Try moving calloc.free(vc); to the end of the main function in Dart.

Kind regards,

 •  Daco Harkes
 •  Software Engineer
 •  dacoh...@google.com 


--
For more ways to connect visit https://dart.dev/community
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.
To view this discussion on the web visit https://groups.google.com/a/dartlang.org/d/msgid/misc/ccbb455c-8d0c-4519-853d-adf3db7d4123n%40dartlang.org.

Maarten Boekhold

unread,
Dec 9, 2021, 5:18:50 AM12/9/21
to Dart Misc, dacoh...@google.com, Maarten Boekhold

Hi Daco, thanks for taking the time to respond. I moved calloc.free(vc); to the end of the test program, after the sane_exit() call. There is no improvement unfortunately, still get the SEGFAULT.

Maarten

Daco Harkes

unread,
Dec 9, 2021, 5:34:01 AM12/9/21
to Maarten Boekhold, Dart Misc
Hi Maarten,

Given that the prints are working in Dart and it only crashes on the sane_exit call, the next thing I would do next is run the native debugger (gdb if you're on Linux).
gdb --args dart your/dart/script.dart and see where it crashes in the native code.

What platform are you targeting?
Can you reproduce it in Dart standalone on a desktop platform?
Can you upload a minimal example to repo?

Kind regards,

 •  Daco Harkes
 •  Software Engineer
 •  dacoh...@google.com 

Maarten Boekhold

unread,
Dec 9, 2021, 5:55:31 AM12/9/21
to Daco Harkes, Dart Misc

Hi Daco,
Currently, I’m targeting a standalone Dart application on Linux, Ubuntu Mate 20.04:

$ uname -a
Linux cytro 5.4.0-91-generic #102-Ubuntu SMP Fri Nov 5 16:31:28 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

I’m using the Dart SDK as included in the Flutter distribution, just upgraded to the latest version, so Dart 2.15. I’m getting the following information from GDB (I’ve stripped obviously irrelevant info out to keep things a bit shorter):

$ gdb --args ../local/flutter/bin/cache/dart-sdk/bin/dart listdevices.dart 
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
...
Reading symbols from ../local/flutter/bin/cache/dart-sdk/bin/dart...
(No debugging symbols found in ../local/flutter/bin/cache/dart-sdk/bin/dart)
(gdb) run
Starting program: /home/boekhold/local/flutter/bin/cache/dart-sdk/bin/dart listdevices.dart
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7c40700 (LWP 909223)]
[New Thread 0x7ffff762f700 (LWP 909224)]
[New Thread 0x7ffff752e700 (LWP 909225)]
[New Thread 0x7ffff257f700 (LWP 909227)]
[New Thread 0x7ffff247e700 (LWP 909228)]
[New Thread 0x7ffff227f700 (LWP 909229)]
[New Thread 0x7ffff1eff700 (LWP 909230)]
[New Thread 0x7fffef57f700 (LWP 909231)]
[New Thread 0x7ffff06eb700 (LWP 909240)]
[Thread 0x7ffff06eb700 (LWP 909240) exited]
[New Thread 0x7ffff06eb700 (LWP 909241)]
[Detaching after vfork from child process 909242]
[Detaching after vfork from child process 909244]
[Thread 0x7ffff06eb700 (LWP 909241) exited]
[New Thread 0x7ffff06eb700 (LWP 909250)]
[Thread 0x7ffff06eb700 (LWP 909250) exited]
test:0
test:1
escl:https://192.168.70.10:443
escl:http://192.168.70.10:443
epsonscan2:networkscanner:esci2:network:192.168.70.10
imagescan:esci:networkscan://192.168.70.10:1865

Thread 9 "DartWorker" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffef57f700 (LWP 909231)]
0x00007ffff0eab160 in ?? ()
(gdb) bt
#0  0x00007ffff0eab160 in ?? ()
#1  0x00007ffff7f8c5a1 in __nptl_deallocate_tsd () at pthread_create.c:301
#2  0x00007ffff7f8d62a in __nptl_deallocate_tsd () at pthread_create.c:256
#3  start_thread (arg=<optimized out>) at pthread_create.c:488
#4  0x00007ffff7d65293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb)

I’m not really sure how to go further from here, it’s been ages since I’ve used GDB…

I can create a small repo on github if this helps, but the original pubspec.yaml and listdevices.dart from my first post is all that’s needed to run this. Let me know if you want me to create the repo however and I’ll do it.

If you do want to try this and you don’t have access to a scanner, let me know, I can help you enable the SANE “test” devices so you can test without scanner (basically, uncomment the “#test“ line in /etc/sane.d/dll.conf).

Maarten

Daco Harkes

unread,
Dec 9, 2021, 6:19:20 AM12/9/21
to Maarten Boekhold, Dart Misc
Hi Maarten,

A quick search on __nptl_deallocate_tsd yields me a result on the sane library [1], so we're not the first to run into something like this. But it doesn't immediately ring a bell with regards to Dart VM threads. I'll have to dive into this to see what is going on.

Please make repo + instructions on how to get/install/build the sane library (I have a Linux machine as well) and how to repro without a test scanner. And file a bug on the Dart SDK and tag me in it.


Kind regards,

 •  Daco Harkes
 •  Software Engineer
 •  dacoh...@google.com 
Reply all
Reply to author
Forward
0 new messages