Skip to content

Further Makefile Improvements#13

Merged
stef merged 7 commits into
stef:masterfrom
flynn162:build
Aug 21, 2025
Merged

Further Makefile Improvements#13
stef merged 7 commits into
stef:masterfrom
flynn162:build

Conversation

@flynn162
Copy link
Copy Markdown
Contributor

@flynn162 flynn162 commented Jul 2, 2025

Remove noise_xk/liboprf-noiseXK.so from the GCC input arguments as it is redundant.

Add a "merged" object file and a "merged localized" object file. The former exposes all "hidden" functions and should be useful for testing. The latter marks all hidden functions "local hidden" which makes it suitable for static linking with client code.

liboprf_merged.o
[...]
   446: 000000000001f320    10 FUNC    GLOBAL DEFAULT    2 crypto_kdf_hkdf_sha256_keybytes
   447: 0000000000007f00   879 FUNC    GLOBAL DEFAULT    2 tpdkg_cheater_msg
   448: 00000000000033b0   269 FUNC    GLOBAL HIDDEN     2 fail
   449: 0000000000003d00   490 FUNC    GLOBAL DEFAULT    2 tpdkg_tp_peer_msg
   450: 0000000000002e70   999 FUNC    GLOBAL DEFAULT    2 dkg_vss_reconstruct
   451: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND crypto_auth_verify
   452: 0000000000017420  1439 FUNC    GLOBAL DEFAULT    2 toprf_update_stp_peer_msg
   453: 0000000000003260   325 FUNC    GLOBAL HIDDEN     2 dump
   454: 000000000000af50     9 FUNC    GLOBAL DEFAULT    2 stp_dkg_peerstate_sessionid
   455: 0000000000015f40   941 FUNC    GLOBAL DEFAULT    2 toprf_update_start_stp
   456: 0000000000003820     9 FUNC    GLOBAL DEFAULT    2 tpdkg_peerstate_lt_sk
   457: 0000000000000840   249 FUNC    GLOBAL DEFAULT    2 voprf_hash_to_group
[...]
liboprf_merged_localized.o
[...]
   446: 00000000000033b0   269 FUNC    LOCAL  HIDDEN     2 fail
   447: 0000000000003260   325 FUNC    LOCAL  HIDDEN     2 dump
   448: 00000000000027c0    61 FUNC    LOCAL  HIDDEN     2 Noise_XK_session_get_key
   449: 0000000000000f60    24 FUNC    LOCAL  HIDDEN     2 coeff
   450: 0000000000000e00   352 FUNC    LOCAL  HIDDEN     2 interpolate
   451: 0000000000001ea0   142 FUNC    LOCAL  HIDDEN     2 send_msg
   452: 0000000000000008     4 OBJECT  LOCAL  HIDDEN    12 debug
   453: 00000000000016a0   292 FUNC    LOCAL  HIDDEN     2 polynom
   454: 0000000000001e20   119 FUNC    LOCAL  HIDDEN     2 check_ts
   455: 00000000000034c0    24 FUNC    LOCAL  HIDDEN     2 htonll
   456: 0000000000001f30   402 FUNC    LOCAL  HIDDEN     2 recv_msg
   457: 00000000000034e0    24 FUNC    LOCAL  HIDDEN     2 ntohll
   458: 0000000000000020    32 OBJECT  LOCAL  HIDDEN     4 H
   459: 0000000000000c70   388 FUNC    LOCAL  HIDDEN     2 lcoeff
   460: 0000000000002800   102 FUNC    LOCAL  HIDDEN     2 update_transcript
   461: 0000000000000000     8 OBJECT  LOCAL  HIDDEN    12 log_file
   462: 00000000000083d0  3171 FUNC    LOCAL  HIDDEN     2 invertedVDMmatrix
   463: 000000000001f320    10 FUNC    GLOBAL DEFAULT    2 crypto_kdf_hkdf_sha256_keybytes
   464: 0000000000007f00   879 FUNC    GLOBAL DEFAULT    2 tpdkg_cheater_msg
[...]

@stef
Copy link
Copy Markdown
Owner

stef commented Jul 2, 2025

how would these "merged.o" files fit or "synergize" with the static lib target that is already there?

@flynn162
Copy link
Copy Markdown
Contributor Author

flynn162 commented Jul 3, 2025

This is a followup to the symbol hiding work we did earlier. The "merged localized" object file has private symbols set to LOCAL HIDDEN (as opposed to GLOBAL HIDDEN). If we pack the localized object file into an archive, that archive is ready for client-facing use cases. Downstream clients cannot link to the "localized" private functions by accident.

The "merged" object file is a temporary side product. GNU's objdump tool only takes 1 file at a time so we will need to merge all objects with partial linking first (ld -r). The "merged" object is functionally equivalent to the .a file that is already produced.

There is a blocker that prevented me from converting these merged objects into static archives. The tests makefile would pass ../dkg.c and liboprf.a in the same compilation command (which is technically incorrect). LD will complain about redundant definitions if I swap out the .a for the merged object, but somehow LD is able to deduplicate the definitions if we pack the archive with original object filenames.

@flynn162
Copy link
Copy Markdown
Contributor Author

flynn162 commented Jul 3, 2025

Link to the Makefile

dkg: ../dkg.c ../utils.c dkg.c ../dkg.c ../liboprf.a ../utils.c
$(CC) $(CFLAGS) -g $(INCLUDES) -DUNIT_TEST -o dkg dkg.c ../dkg.c ../utils.c ../liboprf.a ../noise_xk/liboprf-noiseXK.a -lsodium

@stef
Copy link
Copy Markdown
Owner

stef commented Jul 3, 2025

i do get this. and i very much appreciate this. what i'm missing, is how is this merged localized obj file related to the static archive. should this object file not replace the static archive after all?

@flynn162
Copy link
Copy Markdown
Contributor Author

flynn162 commented Jul 3, 2025

The merged-localized object does not expose any global hidden functions whereas the current .a file does.

Visibility flags in archived objects are interpreted differently than the dynamic linking case. This is partly because .a and .o files do not have a dynsym table. GCC will try to link against global hidden functions that may exist in a static archive (which may cause symbol clashes in the client). For this reason the global hidden symbols must be turned into local symbols if we are building a static library for "release" and not for debugging or testing.

I have a demo here:

vis.h
// vis.h
#pragma once
#include <stdint.h>
int check_timestamp(void);
uint8_t* get_key(void);
vis_test.c
// vis_test.c
#include <stdint.h>
#include <stdlib.h>
#include "vis.h"
[[gnu::visibility("hidden")]] int check_timestamp(void) {
    return -2;
};
[[gnu::visibility("hidden")]] uint8_t *get_key(void) {
    uint8_t* mem = (uint8_t*)malloc(2);
    if (mem == 0) { return 0; }
    mem[0] = 0xFD;
    mem[1] = 0xFE;
    return mem;
};
client.c
$ cat client.c
#include <stdint.h>
#include "vis.h"

int main(void) {
    int res = check_timestamp();
    uint8_t* mem = get_key();
    return res + mem[0];
}
Bash commands

Build the static archive file: vis_test.c -> vis_test.o -> vis.a

The functions are GLOBAL HIDDEN as expected.

$ gcc -std=gnu17 -g -O2 vis_test.c -c
$ ar rcs vis.a vis_test.o
$ readelf -W -s vis.a

File: vis.a(vis_test.o)

Symbol table '.symtab' contains 12 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS vis_test.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 .text
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 .debug_info
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 .debug_abbrev
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 .debug_loclists
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT   11 .debug_line
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT   13 .debug_str
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT   14 .debug_line_str
     9: 0000000000000000    10 FUNC    GLOBAL HIDDEN     1 check_timestamp
    10: 0000000000000010    36 FUNC    GLOBAL HIDDEN     1 get_key
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND malloc

Compile the client and statically link it with the vis.a archive.

$ gcc -std=gnu17 -g -O2 client.c -c
$ gcc -o out.bin -Wl,--no-undefined -Wl,-z,relro,-z,now client.o vis.a
$ objdump -d out.bin --disassemble=main --section=.text
out.bin:     file format elf64-x86-64


Disassembly of section .text:

0000000000001060 <main>:
    1060:       f3 0f 1e fa             endbr64 
    1064:       53                      push   %rbx
    1065:       e8 06 01 00 00          call   1170 <check_timestamp>
    106a:       89 c3                   mov    %eax,%ebx
    106c:       e8 0f 01 00 00          call   1180 <get_key>
    1071:       0f b6 00                movzbl (%rax),%eax
    1074:       01 d8                   add    %ebx,%eax
    1076:       5b                      pop    %rbx
    1077:       c3                      ret
$ ./out.bin ; echo $?
251

Both check_timestamp and get_key are extern-hidden functions, but they still can be linked against by the client. Their code was compiled into the final executable. In this case the client cannot use their own check_timestamp and get_key functions because they clashed with the static library's private functions

0000000000001170 <check_timestamp>:
    1170:       f3 0f 1e fa             endbr64 
    1174:       b8 fe ff ff ff          mov    $0xfffffffe,%eax
    1179:       c3                      ret    
    117a:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

0000000000001180 <get_key>:
    1180:       f3 0f 1e fa             endbr64 
    1184:       48 83 ec 08             sub    $0x8,%rsp
    1188:       bf 02 00 00 00          mov    $0x2,%edi
    118d:       e8 be fe ff ff          call   1050 <malloc@plt>
    1192:       48 85 c0                test   %rax,%rax
    1195:       74 08                   je     119f <get_key+0x1f>
    1197:       ba fd fe ff ff          mov    $0xfffffefd,%edx
    119c:       66 89 10                mov    %dx,(%rax)
    119f:       48 83 c4 08             add    $0x8,%rsp
    11a3:       c3                      ret    

@stef
Copy link
Copy Markdown
Owner

stef commented Jul 3, 2025

yes. thank you for this verbose answer, and apologies for not being clear with my question. i don't care much about the static lib for testing/debugging. we can just directly link to the individual .o files from the test/ directory - i only link to the static lib out of laziness, if we replace this all-visible-syms static lib with one where the internals are eclipsed, then i have no issue with replacing the liboprf.a linkage in test/ with the respective .o files. my question is, if we already have this merged-localized object file, it would be welcome to turn this into a proper static library, for release and inclusion in distros (as it is already in debian for example). so my question is, why not make this merged-localized .o into a proper .a?

@flynn162
Copy link
Copy Markdown
Contributor Author

flynn162 commented Jul 3, 2025

The reason that I didn't package it into a proper .a file is because the tests will not compile if I overwrote the existing ar rcs rule to use the localized object.

It looks like the current direction is to properly package a static archive file for release use, and fix our tests so that it doesn't try to link against the archive file (which is built for release purposes now)

It should be a simple fix: Just add another rule like this in the main makefile:

liboprf_release.$(STATICEXT): liboprf_merged_localized.o
	$(AR) rcs $@ $^

But what do we do with the liboprf.a file that is used in tests? 🤔 Maybe we also need to fix the tests makefile one day.

@stef
Copy link
Copy Markdown
Owner

stef commented Jul 3, 2025

But what do we do with the liboprf.a file that is used in tests? 🤔 Maybe we also need to fix the tests makefile one day.

nah, i'd just link the .o files directly instead of the .a. as said, i just used the .a because i was lazy. i shouldn't be. i should be exact and accurate.

flynn162 added 5 commits July 3, 2025 20:37
- Build all objects explicitly. The rules are generated by putting `eval` inside a `foreach` loop. Guides for writing dynamic rules are in the comments.

- `$(info )` : The space is strictly required.
- Pass in `-DSODIUM_STATIC=1 -DSODIUM_EXPORT=""` to hide the libsodium symbols that we vendorized into liboprf
- In the previous commit, `-fvisibility=hidden` was over-ruled by SODIUM_EXPORT
- Ref: <https://libsodium.gitbook.io/doc/usage>
@flynn162
Copy link
Copy Markdown
Contributor Author

flynn162 commented Jul 4, 2025

I packaged the merged-localized object into liboprf_release.a. We can retire ./liboprf.a once we fixed the tests makefile.

During installation, liboprf_release.a will be renamed to liboprf.a after it is copied.

All translation units are now explicitly built. Source files in the master branch are built twice because liboprf.so requested the source files as deps. For consistency, it should depend on the translation units (objects) instead.

Added terminal colors (decor, endDecor). Can be commented out if the escape strings are causing problems.

Finally, there is a way to hide the libsodium symbols without changing the source. The define flags are officially documented here.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jul 4, 2025

@stef
Copy link
Copy Markdown
Owner

stef commented Jul 4, 2025

most of this makes sense to me, however ce5671d introduces an unnecessary layer of abstraction which gives me bad vibes. you say this is explicit. explicit for me would be:

$target1: $deps1
     $buildcmd1 $args1

but that is bad for maintainability (see below for uninstall deps missing). i don't really see what is the benefit of this abstraction you proposed and if it is proportional to the extra cognitive load understanding the whole thing. afaics this is only for the case when we have to compile the extrasources with vis hidden, for that we can have one explicit rule:

aux_/kdf_hkdf_sha256.o: aux_/kdf_hkdf_sha256.c
     $(CC) $(CFLAGS) -fvisibility=hidden $(INCLUDES) -o $@ -c $^

i would love to merge the rest though. i notice (my fault!) that the uninstall target does not uninstall

$(DESTDIR)$(PREFIX)/lib/pkgconfig/liboprf.pc \
$(DESTDIR)$(PREFIX)/include/oprf/tp-dkg.h \
$(DESTDIR)$(PREFIX)/include/oprf/stp-dkg.h \

i will add these when i merge (and i'll fix the refs to liboprf.a from the test/makefile when doing so)

@stef stef merged commit 68e268f into stef:master Aug 21, 2025
4 checks passed
@stef
Copy link
Copy Markdown
Owner

stef commented Aug 21, 2025

thank you for your patience with this. i had some tragic events that made me have to take a break from a lot of things. in the final version of the merge i did the changes i proposed above, like replace liboprf.a refs with explicit refs to ../XXX.c files in the tests/makefile, replacing the dynamic rules section with an explicit section for compiling the vendored sodium file, and i added the missing uninstall files. i think that more or less sums up my changes. thank you so very much for for this PR, these changes are much appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants