Skip to content

Commit

Permalink
[PR-182][Bug] Loading library on Aarch64 fails because pylink attempt…
Browse files Browse the repository at this point in the history
…s to load 32-bit library (#182)

The ARM64 version of the JLink software contains two library files, `libjlinkarm.so` (64 bit)
and `libjlinkarm_arm.so` (32 bit). Pylink attempts to load `libjlinkarm_arm.so` first, which fails
on ARM64 due to the architecture mismatch and causes an error. This change simply
performs a 'test load' of each library file found, skipping if it fails, fixing this issue on ARM64.
  • Loading branch information
FletcherD committed Jul 12, 2024
1 parent b43a021 commit 8f12d5c
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 4 deletions.
21 changes: 20 additions & 1 deletion pylink/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,23 @@ def get_appropriate_windows_sdk_name(cls):
else:
return Library.WINDOWS_32_JLINK_SDK_NAME

@classmethod
def can_load_library(cls, dllpath):
"""Test whether a library is the correct architecture to load.
Args:
cls (Library): the ``Library`` class
dllpath (str): A path to a library.
Returns:
``True`` if the library could be successfully loaded, ``False`` if not.
"""
try:
ctypes.CDLL(dllpath)
return True
except OSError:
return False

@classmethod
def find_library_windows(cls):
"""Loads the SEGGER DLL from the windows installation directory.
Expand Down Expand Up @@ -203,7 +220,9 @@ def find_library_linux(cls):
for fname in fnames:
fpath = os.path.join(directory_name, fname)
if util.is_os_64bit():
if '_x86' not in fname:
if not cls.can_load_library(fpath):
continue
elif '_x86' not in fname:
yield fpath
elif x86_found:
if '_x86' in fname:
Expand Down
57 changes: 54 additions & 3 deletions tests/unit/test_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,13 +880,16 @@ def test_linux_6_10_0_32bit(self, mock_os, mock_load_library, mock_find_library,
@mock.patch('tempfile.NamedTemporaryFile', new=mock.Mock())
@mock.patch('ctypes.util.find_library')
@mock.patch('ctypes.cdll.LoadLibrary')
@mock.patch('ctypes.CDLL')
@mock.patch('pylink.library.os')
def test_linux_6_10_0_64bit(self, mock_os, mock_load_library, mock_find_library, mock_open, mock_is_os_64bit):
def test_linux_6_10_0_64bit(self, mock_os, mock_cdll, mock_load_library,
mock_find_library, mock_open, mock_is_os_64bit):
"""Tests finding the DLL on Linux through the SEGGER application for V6.0.0+ on 64 bit linux.
Args:
self (TestLibrary): the ``TestLibrary`` instance
mock_os (Mock): a mocked version of the ``os`` module
mock_cdll (Mock): a mocked version of the `cdll.CDLL` class constructor
mock_load_library (Mock): a mocked version of the library loader
mock_find_library (Mock): a mocked call to ``ctypes`` find library
mock_open (Mock): mock for mocking the call to ``open()``
Expand All @@ -896,6 +899,7 @@ def test_linux_6_10_0_64bit(self, mock_os, mock_load_library, mock_find_library,
``None``
"""
mock_find_library.return_value = None
mock_cdll.return_value = None
directories = [
'/opt/SEGGER/JLink_Linux_V610d_x86_64/libjlinkarm_x86.so.6.10',
'/opt/SEGGER/JLink_Linux_V610d_x86_64/libjlinkarm.so.6.10',
Expand All @@ -918,6 +922,49 @@ def test_linux_6_10_0_64bit(self, mock_os, mock_load_library, mock_find_library,
lib.unload = mock.Mock()
self.assertEqual(None, lib._path)

@mock.patch('sys.platform', new='linux2')
@mock.patch('pylink.util.is_os_64bit', return_value=True)
@mock.patch('pylink.library.open')
@mock.patch('os.remove', new=mock.Mock())
@mock.patch('tempfile.NamedTemporaryFile', new=mock.Mock())
@mock.patch('ctypes.util.find_library')
@mock.patch('ctypes.cdll.LoadLibrary')
@mock.patch('ctypes.CDLL')
@mock.patch('pylink.library.os')
def test_linux_64bit_no_x86(self, mock_os, mock_cdll, mock_load_library,
mock_find_library, mock_open, mock_is_os_64bit):
"""Tests finding the DLL on Linux when no library name contains 'x86'.
Args:
self (TestLibrary): the ``TestLibrary`` instance
mock_os (Mock): a mocked version of the ``os`` module
mock_cdll (Mock): a mocked version of the `cdll.CDLL` class constructor
mock_load_library (Mock): a mocked version of the library loader
mock_find_library (Mock): a mocked call to ``ctypes`` find library
mock_open (Mock): mock for mocking the call to ``open()``
mock_is_os_64bit (Mock): mock for mocking the call to ``is_os_64bit``, returns True
Returns:
``None``
"""
def on_cdll(name):
if '_arm' in name:
raise OSError

mock_find_library.return_value = None
mock_cdll.side_effect = on_cdll
directories = [
'/opt/SEGGER/JLink_Linux_V610d_x86_64/libjlinkarm_arm.so.6.10',
'/opt/SEGGER/JLink_Linux_V610d_x86_64/libjlinkarm.so.6.10',
]

self.mock_directories(mock_os, directories, '/')

lib = library.Library()
lib.unload = mock.Mock()
load_library_args, load_libary_kwargs = mock_load_library.call_args
self.assertEqual(directories[1], lib._path)

@mock.patch('sys.platform', new='linux')
@mock.patch('pylink.library.open')
@mock.patch('os.remove', new=mock.Mock())
Expand Down Expand Up @@ -960,8 +1007,9 @@ def test_linux_empty(self, mock_os, mock_load_library, mock_find_library, mock_o
@mock.patch('pylink.platform.libc_ver', return_value=('libc', '1.0'))
@mock.patch('ctypes.util.find_library', return_value='libjlinkarm.so.7')
@mock.patch('pylink.library.JLinkarmDlInfo.__init__')
@mock.patch('ctypes.CDLL')
@mock.patch('ctypes.cdll.LoadLibrary')
def test_linux_glibc_unavailable(self, mock_load_library, mock_dlinfo_ctr, mock_find_library,
def test_linux_glibc_unavailable(self, mock_load_library, mock_cdll, mock_dlinfo_ctr, mock_find_library,
mock_libc_ver, mock_is_os_64bit, mock_os, mock_open):
"""Confirms the whole JLinkarmDlInfo code path is not involved when GNU libc
extensions are unavailable on a Linux system, and that we'll successfully fallback
Expand All @@ -974,6 +1022,7 @@ def test_linux_glibc_unavailable(self, mock_load_library, mock_dlinfo_ctr, mock_
to the "search by file name" code path, aka find_library_linux()
- and "successfully load" a mock library file from /opt/SEGGER/JLink
"""
mock_cdll.side_effect = None
directories = [
# Library.find_library_linux() should find this.
'/opt/SEGGER/JLink/libjlinkarm.so.6'
Expand All @@ -999,8 +1048,9 @@ def test_linux_glibc_unavailable(self, mock_load_library, mock_dlinfo_ctr, mock_
@mock.patch('pylink.util.is_os_64bit', return_value=True)
@mock.patch('pylink.platform.libc_ver', return_value=('glibc', '2.34'))
@mock.patch('ctypes.util.find_library')
@mock.patch('ctypes.CDLL')
@mock.patch('ctypes.cdll.LoadLibrary')
def test_linux_dl_unavailable(self, mock_load_library, mock_find_library, mock_libc_ver,
def test_linux_dl_unavailable(self, mock_load_library, mock_cdll, mock_find_library, mock_libc_ver,
mock_is_os_64bit, mock_os, mock_open):
"""Confirms we successfully fallback to the "search by file name" code path when libdl is
unavailable despite the host system presenting itself as POSIX (GNU/Linux).
Expand All @@ -1012,6 +1062,7 @@ def test_linux_dl_unavailable(self, mock_load_library, mock_find_library, mock_l
to the "search by file name" code path, aka find_library_linux()
- and "successfully load" a mock library file from /opt/SEGGER/JLink
"""
mock_cdll.side_effect = None
mock_find_library.side_effect = [
# find_library('jlinkarm')
'libjlinkarm.so.6',
Expand Down

0 comments on commit 8f12d5c

Please sign in to comment.