-
Notifications
You must be signed in to change notification settings - Fork 1k
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 symbol visibility issues, add test for it #1359
base: master
Are you sure you want to change the base?
Conversation
ee45fa1
to
f816729
Compare
Rebased on top of the merged #1356. |
f816729
to
d7b1cea
Compare
d7b1cea
to
be7ccb9
Compare
be7ccb9
to
5e37264
Compare
5e37264
to
0203b09
Compare
Is it possible for a Cirrus persistent worker to do not override environment variables set with the |
Maybe |
0203b09
to
542ea42
Compare
542ea42
to
b80241b
Compare
Rebased to resolve conflicts. |
7f53ffc
to
4ec4220
Compare
Agreed. Rebased and ready to move forward :) |
4ec4220
to
b40e4e3
Compare
f2a6941
to
4f64b9f
Compare
To clarify, the fix is in the first commit. The excerpt from the CI log with an error message from the previous iteration of this branch looks as follows:
Thanks! The |
447334c include: Avoid visibility("default") on Windows (Tim Ruffing) Pull request description: Fixes #1421. See code comments for rationale. Related meta-bug: #1181. This reminds me that we should move forward with #1359. ACKs for top commit: fanquake: ACK 447334c hebasto: ACK 447334c, tested on Ubuntu 24.04 using the following commands: theuni: ACK 447334c Tree-SHA512: aaa47d88fd1b1f85c3e879a2b288c0eb3beebad0cc89e85f05d0b631f83e58d5a324fb441911970865eaa292f6820d03a1b516d6e8de37a87510e2082acc6e28
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code review ACK 44a9392. No opinion on the tests/ci.
src/util_local_visibility.h
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is small enough to keep it in util.h
. Or is there a reason not to keep it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason is to facilitate the further attempts to make all headers self-contained.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I'm just missing it. Can you explain how this will help with #1039?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intention was to avoid bringing the high-level ../include/secp256k1.h
header into lower-level code via util.h
. However, at this point, util.h
is already being included indirectly through other headers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No opinion here, I defer to @real-or-random for preference.
44a9392
to
8ee12da
Compare
@real-or-random's comments have been addressed. |
tools/symbol-check.py
Outdated
|
||
def check_MACHO_exported_functions(library, expected_exports) -> bool: | ||
ok: bool = True | ||
macho_lib: lief.MACHO.Binary = library.concrete |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the calls to .concrete
for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tools/symbol-check.py
Outdated
elif exe_format == lief.Binary.FORMATS.MACHO: | ||
success = check_MACHO_exported_functions(library, grep_exported_symbols()) | ||
else: | ||
print(f'{filename}: unknown executable format') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this code can be reached, because if it is an unknown format, .parse()
will have errored (printing Unknown format
), and then lief.Binary.FORMATS = library.format
will fail:
./tools/symbol-check.py not_a_file
Unknown format
Traceback (most recent call last):
File "./tools/symbol-check.py", line 81, in <module>
exe_format: lief.Binary.FORMATS = library.format
^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'format'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Reworked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this code can be reached,
This will be reachable once LIEF adds support for a new binary format, so I think it was useful to have it.
8ee12da
to
b80743a
Compare
@fanquake's feedback has been addressed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This script can still be simplified. Let me suggest a refactored version later.
tools/symbol-check.py
Outdated
|
||
def check_PE_exported_functions(library, expected_exports) -> bool: | ||
ok: bool = True | ||
for function in library.exported_functions: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reading the docs, I believe that .exported_functions
is from the abstract Binary class, and [entry.name for entry in library.get_export()]
may give the better result for PE because it includes non-function exports? See https://lief.re/doc/latest/formats/pe/python.html#export-entry
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggested approach returns the same list of symbols on the master branch. Incorporated as a potentially superior one.
tools/symbol-check.py
Outdated
|
||
def check_MACHO_exported_functions(library, expected_exports) -> bool: | ||
ok: bool = True | ||
for function in library.exported_functions: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reading the docs, I believe that .exported_functions
is from the abstract Binary class, and .exported_symbols
may give a better result for Mach-O because it includes non-function exports?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggested approach returns the same list of symbols on the master branch. Incorporated as a potentially superior one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, just checking: You're saying that this change didn't make a difference, right? So it still finds only exported functions and not exported variables?
(And I have the same question for PE.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, just checking: You're saying that this change didn't make a difference, right?
Correct.
So it still finds only exported functions and not exported variables?
With the reverted second commit, both variants catch the same local variables:
./tools/symbol-check.py: In .libs/libsecp256k1.dylib: Unexpected exported symbols: {'secp256k1_pre_g', 'secp256k1_pre_g_128', 'secp256k1_ecmult_gen_prec_table'}
(And I have the same question for PE.)
With the reverted second commit, both variants do not catch local variables.
For more details, please refer to:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that means that the LIEF API is a bit unexpected. Or we don't understand it.
- For Mach-O,
exported_functions
is the same asexported_symbols
. Both include variables, but I'd expect the former to include only functions. - For PE, there doesn't seem to be a way to find exported variables. By the way, importing variables is rare, but the ellswift example and benchmark import the variable
secp256k1_ellswift_xdh_hash_function_bip324
, so at least we test this in CI. And thus we can be sure that the variables are actually exported in PE, and it's just LIEF that doesn't show them.
Does this match your understanding? Do you think it's worth asking LIEF about the variables?
Relatedly, on platforms where LIEF gives us both variables and functions, would it make sense also check that all expected symbols are actually exported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, my analysis of variables is wrong. LIEF shows them. I'll revisit this later.
Here's a refactored version, which is more readable IMHO (haven't tested on Mac or Windows): #!/usr/bin/env python3
"""Check that a libsecp256k1 shared library exports only expected symbols.
Usage examples:
- When building with Autotools:
./tools/symbol-check.py .libs/libsecp256k1.so
./tools/symbol-check.py .libs/libsecp256k1-<V>.dll
./tools/symbol-check.py .libs/libsecp256k1.dylib
- When building with CMake:
./tools/symbol-check.py build/lib/libsecp256k1.so
./tools/symbol-check.py build/bin/libsecp256k1-<V>.dll
./tools/symbol-check.py build/lib/libsecp256k1.dylib"""
import re
import sys
import subprocess
import lief
class UnexpectedExport(RuntimeError):
pass
def get_exported_exports(library) -> list[str]:
"""Adapter function to get exported symbols based on the library format."""
if library.format == lief.Binary.FORMATS.ELF:
return [symbol.name for symbol in library.exported_symbols]
elif library.format == lief.Binary.FORMATS.PE:
return [function.name for function in library.exported_functions]
elif library.format == lief.Binary.FORMATS.MACHO:
return [function.name[1:] for function in library.exported_functions]
raise NotImplementedError(f"Unsupported format: {library.format}")
def grep_expected_symbols() -> list[str]:
"""Guess the list of expected exported symbols from the source code."""
grep_output = subprocess.check_output(
["git", "grep", "^SECP256K1_API", "--", "include"], # TODO WHITESPACE
universal_newlines=True,
encoding="utf-8"
)
lines = grep_output.split("\n")
pattern = re.compile(r'\bsecp256k1_\w+')
exported: list[str] = [pattern.findall(line)[-1] for line in lines if line.strip()]
return exported
def check_symbols(library, expected_exports) -> None:
"""Check that the library exports only the expected symbols."""
actual_exports = list(get_exported_exports(library))
unexpected_exports = set(actual_exports) - set(expected_exports)
if unexpected_exports != set():
raise UnexpectedExport(f"Unexpected exported symbols: {unexpected_exports}")
def main():
if len(sys.argv) != 2:
print(__doc__)
return 1
library = lief.parse(sys.argv[1])
expected_exports = grep_expected_symbols()
try:
check_symbols(library, expected_exports)
except UnexpectedExport as e:
print(f"{sys.argv[0]}: In {sys.argv[1]}: {e}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main()) |
b80743a
to
0c9a046
Compare
Tested on macOS and on Windows. Incorporated. Thank you! |
0c9a046
to
fb5d25d
Compare
tools/symbol-check.py
Outdated
def grep_expected_symbols() -> list[str]: | ||
"""Guess the list of expected exported symbols from the source code.""" | ||
grep_output = subprocess.check_output( | ||
["git", "grep", "^\s*SECP256K1_API", "--", "include"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This gives me error symbol-check.backup.py:40: SyntaxWarning: invalid escape sequence '\s'
. I think it should be
r"^\s*SECP256K1_API"
or
"^\\s*SECP256K1_API"
This change makes the `-fvisibility=hidden` compiler option unnecessary.
Co-authored-by: Tim Ruffing <[email protected]>
fb5d25d
to
11d0ec6
Compare
Rebased and addressed @jonasnick's #1359 (comment). |
11d0ec6
to
7ac46cf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code-review reACK 7ac46cf.
Still no opinion on the .py/ci.
Being able to drop -fvisibility=hidden
is a nice improvement (and also a testament to the code structure!)
Closes #1181.
Catches the actual symbol visibility issue.
Replaces #1135.