Passed
Push — master ( aeb165...d9ae97 )
by Dean
03:04
created

SystemHelper.elf_attributes()   D

Complexity

Conditions 8

Size

Total Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 13.4517
Metric Value
dl 0
loc 45
ccs 14
cts 25
cp 0.56
rs 4
cc 8
crap 13.4517
1 1
from plugin.core.configuration import Configuration
2 1
from plugin.core.libraries.helpers.arm import ArmHelper
3
4 1
from elftools.elf.attributes import AttributesSection
5 1
from elftools.elf.elffile import ELFFile
6 1
import logging
7 1
import os
8 1
import platform
9 1
import sys
10
11 1
log = logging.getLogger(__name__)
12
13 1
BITS_MAP = {
14
    '32bit': 'i386',
15
    '64bit': 'x86_64'
16
}
17
18 1
MACHINE_MAP = {
19
    ('32bit', 'i686'):  'i686',
20
    ('32bit', 'ppc' ):  'PowerPC'
21
}
22
23 1
MSVCR_MAP = {
24
    'msvcr120.dll': 'vc12',
25
    'msvcr130.dll': 'vc14'
26
}
27
28 1
NAME_MAP = {
29
    'Darwin': 'MacOSX'
30
}
31
32 1
FALLBACK_EXECUTABLE = '/bin/ls'
33
34
35 1
class SystemHelper(object):
36 1
    @classmethod
37
    def architecture(cls):
38
        """Retrieve system architecture (i386, i686, x86_64)"""
39
40
        # Use `cpu_architecture` value from advanced configuration (if defined)
41 1
        cpu_architecture = Configuration.advanced['libraries'].get('cpu_architecture')
42
43 1
        if cpu_architecture:
44
            log.info('Using CPU Architecture from advanced configuration: %r', cpu_architecture)
45
            return cpu_architecture
46
47
        # Determine architecture from platform attributes
48 1
        bits, _ = platform.architecture()
49 1
        machine = platform.machine()
50
51
        # Check for ARM machine
52 1
        if bits == '32bit' and machine.startswith('armv'):
53
            return cls.arm(machine)
54
55
        # Check (bits, machine) map
56 1
        machine_key = (bits, machine)
57
58 1
        if machine_key in MACHINE_MAP:
59
            return MACHINE_MAP[machine_key]
60
61
        # Check (bits) map
62 1
        if bits in BITS_MAP:
63 1
            return BITS_MAP[bits]
64
65
        log.error('Unable to determine system architecture - bits: %r, machine: %r', bits, machine)
66
        return None
67
68 1
    @classmethod
69
    def name(cls):
70
        """Retrieve system name (Windows, Linux, FreeBSD, MacOSX)"""
71
72 1
        system = platform.system()
73
74
        # Apply system map
75 1
        if system in NAME_MAP:
76
            system = NAME_MAP[system]
77
78 1
        return system
79
80 1
    @classmethod
81
    def arm(cls, machine):
82
        # Determine floating-point type
83
        float_type = cls.arm_float_type()
84
85
        if float_type is None:
86
            log.warn('Unable to use ARM libraries, unsupported floating-point type?')
87
            return None
88
89
        # Determine ARM version
90
        version = cls.arm_version()
91
92
        if version is None:
93
            log.warn('Unable to use ARM libraries, unsupported ARM version (%r)?' % machine)
94
            return None
95
96
        return '%s_%s' % (version, float_type)
97
98 1
    @classmethod
99 1
    def arm_version(cls, machine=None):
100
        # Read `machine` name if not provided
101
        if machine is None:
102
            machine = platform.machine()
103
104
        # Ensure `machine` is valid
105
        if not machine:
106
            return None
107
108
        # ARMv5
109
        if machine.startswith('armv5'):
110
            return 'armv5'
111
112
        # ARMv6
113
        if machine.startswith('armv6'):
114
            return 'armv6'
115
116
        # ARMv7
117
        if machine.startswith('armv7'):
118
            return 'armv7'
119
120
        return None
121
122 1
    @classmethod
123 1
    def arm_float_type(cls, executable_path=sys.executable):
124
        # Use `arm_float_type` value from advanced configuration (if defined)
125
        arm_float_type = Configuration.advanced['libraries'].get('arm_float_type')
126
127
        if arm_float_type:
128
            log.info('Using ARM Float Type from advanced configuration: %r', arm_float_type)
129
            return arm_float_type
130
131
        # Try determine float-type from "/lib" directories
132
        if os.path.exists('/lib/arm-linux-gnueabihf'):
133
            return 'hf'
134
135
        if os.path.exists('/lib/arm-linux-gnueabi'):
136
            return 'sf'
137
138
        # Determine system float-type from python executable
139
        section, attributes = cls.elf_attributes(executable_path)
140
141
        if not section or not attributes:
142
            return None
143
144
        if section.name != 'aeabi':
145
            log.warn('Unknown attributes section name: %r', section.name)
146
            return None
147
148
        # Assume hard-float if "tag_abi_vfp_args" is present
149
        if attributes.get('abi_vfp_args'):
150
            return 'hf'
151
152
        return 'sf'
153
154 1
    @classmethod
155 1
    def cpu_name(cls, executable_path=sys.executable):
156
        # Retrieve CPU name from ELF
157 1
        section, attributes = cls.elf_attributes(executable_path)
158
159 1
        if not section or not attributes:
160 1
            return None
161
162
        name = attributes.get('cpu_name')
163
164
        if not name:
165
            return None
166
167
        return name.lower()
168
169 1
    @classmethod
170 1
    def cpu_type(cls, executable_path=sys.executable):
171
        # Use `cpu_type` value from advanced configuration (if defined)
172 1
        cpu_type = Configuration.advanced['libraries'].get('cpu_type')
173
174 1
        if cpu_type:
175
            log.info('Using CPU Type from advanced configuration: %r', cpu_type)
176
            return cpu_type
177
178
        # Try retrieve cpu type via "/proc/cpuinfo"
179 1
        try:
180 1
            _, _, cpu_type = ArmHelper.identifier()
181
182 1
            if cpu_type:
183
                return cpu_type
184
        except Exception, ex:
185
            log.warn('Unable to retrieve cpu type from "/proc/cpuinfo": %s', ex, exc_info=True)
186
187
        # Fallback to using the ELF cpu name
188 1
        return cls.cpu_name(executable_path)
189
190 1
    @classmethod
191 1
    def elf_attributes(cls, executable_path=sys.executable):
192 1
        if cls.name() == 'MacOSX':
193
            log.info('Unable to retrieve ELF attributes on Mac OSX (not supported)')
194
            return None, None
195
196
        # Read attributes from "/bin/ls" if `executable_path` doesn't exist
197 1
        if not executable_path or not os.path.exists(executable_path):
198
            log.info('Executable at %r doesn\'t exist, using %r instead', executable_path, FALLBACK_EXECUTABLE)
199
            executable_path = FALLBACK_EXECUTABLE
200
201 1
        try:
202
            # Open executable stream
203 1
            stream = open(executable_path, 'rb')
204
205
            # Retrieve magic number (header)
206 1
            magic = stream.read(4)
207
208 1
            if magic != b'\x7fELF':
209
                log.warn('Unknown ELF format for %r (magic: %r)', executable_path, magic)
210
                return None, None
211
212 1
            stream.seek(0)
213
214
            # Parse ELF
215 1
            elf = ELFFile(stream)
216
217
            # Find attributes section
218 1
            section = cls._find_elf_section(elf, AttributesSection)
219
220 1
            if section is None:
221 1
                log.info('Unable to find attributes section in ELF: %r', executable_path)
222 1
                return None, None
223
224
            # Build dictionary of attributes
225
            attributes = dict([
226
                (attr.tag.lower(), attr.value)
227
                for attr in section.iter_attributes()
228
            ])
229
230
            return section, attributes
231
        except Exception, ex:
232
            log.warn('Unable to retrieve attributes from ELF %r: %s', executable_path, ex, exc_info=True)
233
234
        return None, None
235
236 1
    @classmethod
237
    def page_size(cls):
238 1
        try:
239 1
            import resource
240 1
            page_size = resource.getpagesize()
241
242 1
            if not page_size:
243
                return None
244
245 1
            return '%dk' % (page_size / 1024)
246
        except Exception, ex:
247
            log.warn('Unable to retrieve memory page size: %s', ex, exc_info=True)
248
            return None
249
250 1
    @staticmethod
251
    def _find_elf_section(elf, cls):
252 1
        for section in elf.iter_sections():
253 1
            if isinstance(section, cls):
254
                return section
255
256 1
        return None
257
258 1
    @classmethod
259
    def vcr_version(cls):
260
        try:
261
            import ctypes.util
262
263
            # Retrieve linked msvcr dll
264
            name = ctypes.util.find_msvcrt()
265
266
            # Return VC++ version from map
267
            if name not in MSVCR_MAP:
268
                log.error('Unknown VC++ runtime: %r', name)
269
                return None
270
271
            return MSVCR_MAP[name]
272
        except Exception:
273
            log.error('Unable to retrieve VC++ runtime version', exc_info=True)
274
            return None
275