.netmacosxamarin.mac

EXC_BAD_ACCESS (SIGSEGV) Error when Accessing AXUIElementCopyAttributValue() in .NET macOS App


I'm developing a macOS application in .NET 7.0 that utilizes the Accessibility Framework (ApplicationServices -> HIServices) to access assistive features. When attempting to invoke the AXUIElementCopyAttributValues() function, the application crashes with the EXC_BAD_ACCESS (SIGSEGV) error.

Here is my code and a link to my test repository:

public partial class ProgramTest
{
   public void MainTest()
   {       
            AccessibilityHelper.RequestAccessibilityPermission();

            string bundleIdentifier = "com.apple.Safari";

            NSRunningApplication app = NSRunningApplication.GetRunningApplications(bundleIdentifier)?.First();

            if (app != null)
            {
                int processId = app.ProcessIdentifier;

                IntPtr appElement = AXUIElementCreateApplication((uint)processId);

                IntPtr mainWindow;
            if (AXUIElementCopyAttributeValue(appElement, kAXMainWindowAttribute, out mainWindow) == 0)
            {
                Console.WriteLine("Main Window Handle: " + mainWindow);
            }
            else
            {
                Console.WriteLine("Could not retrieve the main window.");
            }
        }
        else
            {
                Console.WriteLine("Application not found.");
            }
        }

        const string accessibilityFramework = "/System/Library/Frameworks/ApplicationServices.framework/Versions/Current/ApplicationServices";
        [DllImport(accessibilityFramework)]
        static extern IntPtr AXUIElementCreateApplication(uint pid);
        [DllImport(accessibilityFramework)]
        static extern int AXUIElementCopyAttributeValue(IntPtr element, string attribute, out IntPtr value);
        const string kAXMainWindowAttribute = "kAXTitleAttribute";
    }

    public static class AccessibilityHelper
    {
        public static void RequestAccessibilityPermission()
        {
            NSString key = new NSString(AXTrustedCheckOptionPrompt);
            NSDictionary options = NSDictionary.FromObjectAndKey(NSNumber.FromBoolean(true), key);

            IntPtr optionsPtr = options.Handle;
            bool isTrusted = AXIsProcessTrustedWithOptions(optionsPtr);

            if (!isTrusted)
            {
                Console.WriteLine("User denied accessibility permission.");
            }
        }

        const string accessibilityFramework = "/System/Library/Frameworks/ApplicationServices.framework/Versions/Current/ApplicationServices";
        [DllImport(accessibilityFramework)]
        static extern bool AXIsProcessTrustedWithOptions(IntPtr options);
        const string AXTrustedCheckOptionPrompt = "AXTrustedCheckOptionPrompt";
    }

Here are the steps I've taken to address the issue:

Info.plist Entries:

I've added the required keys for Accessibility Permissions in my Info.plist file:

<key>NSAppleEventsUsageDescription</key>
<string>We need accessibility permissions to interact with other applications.</string>
<key>NSAccessibilityUsageDescription</key>
<string>We need accessibility permissions to interact with other applications.</string>
Requesting Permissions at Runtime:

I implemented a RequestAccessibilityPermission() method to request Accessibility Permissions at runtime.

 public static void RequestAccessibilityPermission()
 {
     NSString key = new NSString(AXTrustedCheckOptionPrompt);
     NSDictionary options = NSDictionary.FromObjectAndKey(NSNumber.FromBoolean(true), key);

     IntPtr optionsPtr = options.Handle;
     bool isTrusted = AXIsProcessTrustedWithOptions(optionsPtr);

     if (!isTrusted)
     {
         Console.WriteLine("User denied accessibility permission.");
     }
 }
Custom Library

I've also created a custom Cocoa Library using Xcode with the methods AXUIElementCreateApplication(_:) and AXUIElementCopyAttributeValue(_:_:_:). I intend to use these methods through P/Invoke in my .NET 7.0 macOS application. However, I'm encountering the same EXC_BAD_ACCESS (SIGSEGV) error even when using this custom Cocoa Library.

Entitlements

Additionally, I have disabled the Sandbox in the entitlements.

<key>com.apple.security.app-sandbox</key>
<false/>
Error

Here is the error report

-------------------------------------
Translated Report (Full Report Below)
-------------------------------------

Process:               netMacOSTest [70764]
Path:                  /Users/USER/*/netMacOSTest.app/Contents/MacOS/netMacOSTest
Identifier:            com.companyname.netMacOSTest
Version:               1.0 (1)
Code Type:             X86-64 (Translated)
Parent Process:        vsdbg-ui [70763]
Responsible:           VisualStudio [491]
User ID:               501

Date/Time:             2023-12-07 15:44:18.6174 +0100
OS Version:            macOS 14.0 (23A344)
Report Version:        12
Anonymous UUID:        E66ED467-F839-D06D-428B-00BCDEEAD568


Time Awake Since Boot: 610000 seconds

System Integrity Protection: enabled

Notes:
PC register does not match crashing frame (0x0 vs 0x10344B8E8)

Crashed Thread:        0  Dispatch queue: com.apple.main-thread

Exception Type:        EXC_BAD_ACCESS (SIGSEGV)
Exception Codes:       KERN_INVALID_ADDRESS at 0x0000746954584180
Exception Codes:       0x0000000000000001, 0x0000746954584180

Termination Reason:    Namespace SIGNAL, Code 11 Segmentation fault: 11
Terminating Process:   exc handler [70764]

VM Region Info: 0x746954584180 is not in any region.  Bytes after previous region: 22442082320769  Bytes before following region: 12514185690752
      REGION TYPE                    START - END         [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      MALLOC_NANO              600000000000-600020000000 [512.0M] rw-/rwx SM=PRV  
--->  GAP OF 0x1fcae3f00000 BYTES
      MALLOC_TINY              7fcb03f00000-7fcb04000000 [ 1024K] rw-/rwx SM=PRV  

Error Formulating Crash Report:
PC register does not match crashing frame (0x0 vs 0x10344B8E8)

Thread 0 Crashed::  Dispatch queue: com.apple.main-thread
0   <translation info unavailable>         0x10344b8e8 ???
1   HIServices                          0x7ff81c30f1e0 AXUIElementCopyAttributeValue + 95
2   ???                                    0x116ecfdbe ???
3   ???                                    0x116ecafd2 ???
4   ???                                    0x116ecae4a ???
5   libcoreclr.dylib                       0x10d478d89 CallDescrWorkerInternal + 124
6   libcoreclr.dylib                       0x10d478d89 CallDescrWorkerInternal + 124

Does anyone have experience with this issue and can provide advice on how to resolve it? Are there additional steps I should take to identify and address the root cause of the error?


Solution

  • It looks like your method definition isn't entirely correct.

    I just ran some test with the below import definitions which were successful. (note - you'll need to allow your code to execute unsafe code for these due to the pointer useage)

    [DllImport("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")]
    static extern IntPtr AXUIElementCreateSystemWide();
    
    [DllImport("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")]
    unsafe static extern int AXUIElementCopyAttributeNames(IntPtr element, IntPtr* names);
    
    [DllImport("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")]
    unsafe static extern int AXUIElementCopyAttributeValue(IntPtr element, IntPtr attribute, IntPtr* value);
    

    These look to be working for me, see examples below. Note that the attribute names you should use don't match with the exact const definition in the header file but with the CopyAttributeNames you're able to validate what possible keys there are.

    After changing the method definition I started getting correct error codes or results back instead of getting crashes on memory faults.

    // get accessibility element
    var element = AXUIElementCreateSystemWide();
    
    // fetch the available attributes
    IntPtr ptrAttributeNames;
    var attributeNamesError = AXUIElementCopyAttributeNames(element, &ptrAttributeNames);
    
    // convert response to a readable array
    var attributeNames = CFArray.ArrayFromHandleFunc(ptrAttributeNames, CFString.FromHandle);
    
    // fetch the value for an attribute
    IntPtr ptrAttributeValue;
    var attributeValueError = AXUIElementCopyAttributeValue(element, CFString.CreateNative("AXSelectedText"), &ptrAttributeValue);
    var attributeValue = CFString.FromHandle(ptrAttributeValue);