Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Loading library on Aarch64 fails because pylink attempts to load 32-bit library #182

Merged
merged 6 commits into from
Jul 12, 2024
Merged
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
Loading