Microsoft MVP성태의 닷넷 이야기
Linux: 101. eBPF 함수의 인자를 다루는 방법 [링크 복사], [링크+제목 복사],
조회: 5360
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)

eBPF 함수의 인자를 다루는 방법

지난 글에 실습한 kprobe 예제는,

SEC("kprobe/sys_clone") int kprobe_sys_clone(void *ctx)
{
    u64 pid_tgid = bpf_get_current_pid_tgid();
    pid_t tgid = pid_tgid >> 32;
    pid_t pid = pid_tgid;

    bpf_printk("pid == %d, thread_id == %d, newsp == %x\n", tgid, pid, clone_flags);
    return -1;
}

인자를 void* 타입으로 퉁쳤었는데요, 이제 그것을 수정해 보겠습니다. 이를 위해 우선 kprobe 대상 함수("sys_clone")의 인자를 확인해 보면,

// https://github.com/torvalds/linux/blob/ae90f6a6170d7a7a1aa4fddf664fbd093e3023bc/include/linux/syscalls.h#L786

#ifdef CONFIG_CLONE_BACKWARDS
asmlinkage long sys_clone(unsigned long, unsigned long, int __user *, unsigned long,
           int __user *);
#else
#ifdef CONFIG_CLONE_BACKWARDS3
asmlinkage long sys_clone(unsigned long, unsigned long, int, int __user *,
              int __user *, unsigned long);
#else
asmlinkage long sys_clone(unsigned long, unsigned long, int __user *,
           int __user *, unsigned long);
#endif

5개의 인자가 나오는데요, 이것을 접근하는 방법은 eBPF 함수에 기본적으로 전달되는 pt_regs를 이용해 PT_REGS_PARAM 매크로를 사용하면 됩니다.

SEC("kprobe/sys_clone") int kprobe_sys_clone(struct pt_regs* ctx)
{
    unsigned long clone_flags = PT_REGS_PARM1(ctx);
    unsigned long newsp = PT_REGS_PARM2(ctx);
    int* parent_tidptr = (int*)PT_REGS_PARM3(ctx);
    unsigned long tls = PT_REGS_PARM4(ctx);
    int* child_tidptr = (int*)PT_REGS_PARM5(ctx);

    // ...[생략]...
}

여기서 pt_regs는 Win32 환경이라면 CONTEXT 구조체와 비슷한 역할을 합니다. 따라서 CPU 레지스터를 대표하기 때문에 운영체제가 정한 ABI에 따라 레지스터를 통해 들어오는 인자와 Stack Pointer로부터의 오프셋 계산으로 인자를 추출할 수 있습니다.

리눅스의 x86-64 ABI인 경우, 첫 6개의 매개변수를 rdi, rsi, rdx, rcx, r8, r9 레지스터에 전달한다고 합니다. 만약 부동 소수점 타입인 경우에는 xmm 레지스터로 전달하고, 그 이상의 매개변수인 경우에는 스택을 이용합니다.




위와 같은 접근법이 libbpf를 도입하면 약간 바뀌는데요, 이에 대해서는 설명한 적이 있습니다.

eBPF의 2가지 방식 - libbcc와 libbpf(CO-RE) - 8. Kprobes
; https://www.sysnet.pe.kr/2/0/13801#8

즉, BPF_KPROBE 매크로 함수를 사용해 pt_regs를 숨기고 인자를 직접 지정할 수 있는데,

#ifdef __BCC__
int kprobe__acct_collect(struct pt_regs *ctx, long exit_code, int group_dead)
#else
SEC("kprobe/acct_collect")
int BPF_KPROBE(kprobe__acct_collect, long exit_code, int group_dead)
#endif
{
    /* BPF code accessing exit_code and group_dead here */
}

원한다면 일부 인자만 정의해도 됩니다. 가령 kprobe_sys_clone 함수를 첫 번째 인자(clone_flags)만 관심이 있다면 이렇게 정의할 수 있습니다.

SEC("kprobe/sys_clone") int BPF_KPROBE(kprobe_sys_clone, unsigned long clone_flags)
{
    bpf_printk("clone_flags == %x\n", clone_flags);
    return 0;
}

그런데 사실 pt_regs 인자가 없어진 것이 아닙니다. 이를 위해 BPF_KPROBE 매크로 정의를 보면,

#define BPF_KPROBE(name, args...)                       \
name(struct pt_regs *ctx);                          \
static __always_inline typeof(name(0))                      \
____##name(struct pt_regs *ctx, ##args);                    \
typeof(name(0)) name(struct pt_regs *ctx)                   \
{                                       \
    _Pragma("GCC diagnostic push")                      \
    _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")          \
    return ____##name(___bpf_kprobe_args(args));                \
    _Pragma("GCC diagnostic pop")                       \
}   

pt_regs 인자를 ctx라는 숨겨진 변수로 처리하고 있습니다. 결국 다음과 같이 BPF_KPROBE의 인자 지정과 함께 혼용하는 것도 가능합니다.

// 이렇게 사용하는 것도 가능
SEC("kprobe/sys_clone") int BPF_KPROBE(kprobe_sys_clone, unsigned long clone_flags)
{
    unsigned long newsp = PT_REGS_PARM2(ctx); // BPF_KPROBE에 지정하지 않은 2번째 인자를 매크로로 추출
    bpf_printk("clone_flags == %x, newsp == %x\n", clone_flags, newsp);

    return 0;
}

다시 말해, pt_regs* ctx가 없어진 것이 아니라 그냥 가볍게 감싼 것에 불과하기 때문에 PT_REGS_PARM 매크로를 이용한 방법은 여전히 유효합니다.

SEC("kprobe/sys_clone") int BPF_KPROBE(kprobe_sys_clone, unsigned long clone_flags, unsigned long newsp, int* parent_tidptr, unsigned long tls, int* child_tidptr)
{
    unsigned long clone_flags2 = PT_REGS_PARM1(ctx);
    bpf_printk("clone_flags == %x, clone_flags2 == %x\n", clone_flags, clone_flags2);

    unsigned long newsp2 = PT_REGS_PARM2(ctx);
    bpf_printk("newsp == %x, newsp2 == %x\n", newsp, newsp2);

    int* parent_tidptr2 = (int*)PT_REGS_PARM3(ctx);
    bpf_printk("parent_tidptr == %x, parent_tidptr2 == %x\n", parent_tidptr, parent_tidptr2);

    unsigned long tls2 = PT_REGS_PARM4(ctx);
    bpf_printk("tls == %x, tls2 == %x\n", tls, tls2);

    int* child_tidptr2 = (int*)PT_REGS_PARM5(ctx);
    bpf_printk("child_tidptr == %x, child_tidptr2 == %x\n", child_tidptr, child_tidptr2);
    return 0;
}




그럼 커널 구조체도 접근해 볼까요? ^^ 예를 들기 위해 tcp_connect 함수를 대상으로 할 텐데요, 그 함수의 첫 번째 인자는 struct sock *sk입니다.

struct sock
; https://github.com/torvalds/linux/blob/master/include/net/sock.h#L342

struct sock 구조체의 내부에는 다시 struct sock_common 구조체가 있는데요,

struct sock_common
; https://github.com/torvalds/linux/blob/master/include/net/sock.h#L150

물론, 커널 헤더 파일의 정의대로 직접 eBPF 소스코드에 복사해 사용할 수도 있지만 vmlinux.h를 생성하면 저 구조체들이 포함돼 있으므로 그걸 활용하는 것이 더 깔끔합니다.

$ grep -A 5 "struct sock {" vmlinux.h
struct sock {
        struct sock_common __sk_common;
        socket_lock_t sk_lock;
        atomic_t sk_drops;
        int sk_rcvlowat;
        struct sk_buff_head sk_error_queue;

$ grep -A 60 "struct sock_common {" vmlinux.h
struct sock_common {
        union {
                __addrpair skc_addrpair;
                struct {
                        __be32 skc_daddr;
                        __be32 skc_rcv_saddr;
                };
        };
        union {
                unsigned int skc_hash;
                __u16 skc_u16hashes[2];
        };
        union {
                __portpair skc_portpair;
                struct {
                        __be16 skc_dport;
                        __u16 skc_num;
                };
        };
        short unsigned int skc_family;
        volatile unsigned char skc_state;
        unsigned char skc_reuse: 4;
        unsigned char skc_reuseport: 1;
        unsigned char skc_ipv6only: 1;
        unsigned char skc_net_refcnt: 1;
        int skc_bound_dev_if;
        union {
                struct hlist_node skc_bind_node;
                struct hlist_node skc_portaddr_node;
        };
        struct proto *skc_prot;
        possible_net_t skc_net;
        struct in6_addr skc_v6_daddr;
        struct in6_addr skc_v6_rcv_saddr;
        atomic64_t skc_cookie;
        union {
                long unsigned int skc_flags;
                struct sock *skc_listener;
                struct inet_timewait_death_row *skc_tw_dr;
        };
        int skc_dontcopy_begin[0];
        union {
                struct hlist_node skc_node;
                struct hlist_nulls_node skc_nulls_node;
        };
        short unsigned int skc_tx_queue_mapping;
        short unsigned int skc_rx_queue_mapping;
        union {
                int skc_incoming_cpu;
                u32 skc_rcv_wnd;
                u32 skc_tw_rcv_nxt;
        };
        refcount_t skc_refcnt;
        int skc_dontcopy_end[0];
        union {
                u32 skc_rxhash;
                u32 skc_window_clamp;
                u32 skc_tw_snd_nxt;
        };
};

그럼, 이걸 이용해 eBPF 내부에서 INET/INET6 패밀리만을 추적하도록 코드를 수정할 수 있습니다.

SEC("fentry/tcp_connect")
int BPF_PROG(tcp_connect, struct sock *sk)
{
    if (sk->__sk_common.skc_family != AF_INET &&
        sk->__sk_common.skc_family != AF_INET6)
    {
        return 0;
    }

    pid_t tgid = bpf_get_current_pid_tgid() >> 32;
    bpf_printk("fentry-tcp_connect: pid = %d\n", tgid);
    return 0;
}

물론 저렇게 코딩하는 것은 libbcc의 방법에서 가능한 것이고, libbpf에서라면 bpf_probe_read로 다음과 같이 접근해야 합니다.

// BPF_CORE_READ() - #include <bpf/bpf_core_read.h>
// https://nakryiko.com/posts/bpf-core-reference-guide/#bpf-core-read-1

unsigned short sk_family = BPF_CORE_READ(sk, __sk_common.skc_family);




PT_REGS_PARM1 ~ PT_REGS_PARM5 매크로를 사용한 경우, 또는 BPF_KPROBE 매크로에 의해 내부적으로 PT_REGS_PARM을 사용하는 경우,

SEC("kprobe/sys_clone") int BPF_KPROBE(kprobe_sys_clone, unsigned long clone_flags)
{
    bpf_printk("clone_flags == %x\n", clone_flags);
    return 0;
}

기존 bpf2go 명령어로 컴파일하면,

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go ebpf_basic basic.c

이런 오류가 발생합니다.

$ go generate
/mnt/c/temp/ebpf_sample/basic.c:20:29: error: The eBPF is using target specific macros, please provide -target that is not bpf, bpfel or bpfeb
   20 | SEC("kprobe/sys_clone") int BPF_KPROBE(kprobe_sys_clone, unsigned long clone_flags)
      |                             ^
/usr/include/bpf/bpf_tracing.h:429:20: note: expanded from macro 'BPF_KPROBE'
  429 |         return ____##name(___bpf_kprobe_args(args));                        \
      |                           ^
/usr/include/bpf/bpf_tracing.h:409:2: note: expanded from macro '___bpf_kprobe_args'
  409 |         ___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
      |         ^
/usr/include/bpf/bpf_helpers.h:165:29: note: expanded from macro '___bpf_apply'
  165 | #define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
      |                             ^
note: (skipping 2 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
/usr/include/bpf/bpf_tracing.h:399:33: note: expanded from macro '___bpf_kprobe_args1'
  399 |         ___bpf_kprobe_args0(), (void *)PT_REGS_PARM1(ctx)
      |                                        ^
/usr/include/bpf/bpf_tracing.h:309:29: note: expanded from macro 'PT_REGS_PARM1'
  309 | #define PT_REGS_PARM1(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
      |                             ^
:21:6: note: expanded from here
   21 |  GCC error "The eBPF is using target specific macros, please provide -target that is not bpf, bpfel or bpfeb"
      |      ^
1 error generated.
Error: compile: exit status 1
exit status 1
main.go:3: running "go": exit status 1

왜냐하면, 해당 매크로를 이용하게 되면 더 이상 endian 중립적인 코드를 사용할 수 없기 때문에 명시적으로 대상 플랫폼을 지정해야 하는데요, 이를 위해 "-target" 인자를 추가할 수 있습니다.

The eBPF is using target specific macros, please provide -target #772
; https://github.com/cilium/ebpf/discussions/772

//go2:generate go run github.com/cilium/ebpf/cmd/bpf2go -target amd64 ebpf_basic basic.c

가능한 platform 타깃은 bpf, bpfel, bpfeb, 386, amd64, arm, arm64, loong64, mips, ppc64, ppc64le, riscv64, s390x입니다.

참고로, arm64 타깃으로 컴파일하는 경우,

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -target arm64 ebpf_basic basic.c

이런 오류가 발생합니다.

$ go generate
Compiled /mnt/c/temp/ebpf_sample/ebpf_basic_x86_bpfel.o
Stripped /mnt/c/temp/ebpf_sample/ebpf_basic_x86_bpfel.o
Wrote /mnt/c/temp/ebpf_sample/ebpf_basic_x86_bpfel.go
/mnt/c/temp/ebpf_sample/basic.c:18:33: error: incomplete definition of type 'struct user_pt_regs'
   18 |     unsigned long clone_flags = PT_REGS_PARM1(ctx);
      |                                 ^~~~~~~~~~~~~~~~~~
/usr/include/bpf/bpf_tracing.h:195:49: note: expanded from macro 'PT_REGS_PARM1'
  195 | #define PT_REGS_PARM1(x) (((PT_REGS_ARM64 *)(x))->regs[0])
      |                           ~~~~~~~~~~~~~~~~~~~~~~^
/mnt/c/temp/ebpf_sample/basic.c:18:33: note: forward declaration of 'struct user_pt_regs'
/usr/include/bpf/bpf_tracing.h:195:29: note: expanded from macro 'PT_REGS_PARM1'
  195 | #define PT_REGS_PARM1(x) (((PT_REGS_ARM64 *)(x))->regs[0])
      |                             ^
/usr/include/bpf/bpf_tracing.h:194:45: note: expanded from macro 'PT_REGS_ARM64'
  194 | #define PT_REGS_ARM64 const volatile struct user_pt_regs
      |                                             ^
1 error generated.
Error: compile: exit status 1
exit status 1
main.go:4: running "go": exit status 1

ARM의 경우 CPU 레지스터 구조가 바뀌는데 이를 표현하기 위한 구조체가 user_pt_regs로 정의돼 있습니다. 위의 코드는 PT_REGS_ARM64 매크로가 user_pt_regs를 사용해 펼치긴 했지만 정작 그 구조체가 정의돼 있지 않아 컴파일 오류가 발생한 것입니다. 그런데, 이 오류를 해결하는 방법이 애매합니다. 아마도 ARM64 환경에서 저 빌드를 해야 하는 것 같은데요, 일단 user_pt_regs 구조체의 정의는 다음과 같습니다.

// https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/ptrace.h

struct user_pt_regs {
	__u64		regs[31];
	__u64		sp;
	__u64		pc;
	__u64		pstate;
};




PT_REGS_PARM 매크로를 이용해 인자를 포인터로 받으면,

int* parent_tidptr = PT_REGS_PARM3(ctx);
int value = *parent_tidptr;

이런 컴파일 오류가 발생합니다.

$ go generate
/mnt/c/temp/ebpf_sample/basic.c:28:10: error: incompatible integer to pointer conversion initializing 'int *' with an expression of type 'unsigned long' [-Wint-conversion]
   28 |     int* parent_tidptr = PT_REGS_PARM3(ctx);
      |          ^               ~~~~~~~~~~~~~~~~~~
1 error generated.
Error: compile: exit status 1
exit status 1
main.go:4: running "go": exit status 1

당연히 형변환이 필요한데요,

int* parent_tidptr = (int*)PT_REGS_PARM3(ctx);

문제는, 이렇게 포인터 변수로부터 값을 참조해 사용하면,

unsigned long clone_flags = PT_REGS_PARM1(ctx);
int* parent_tidptr = (int*)PT_REGS_PARM3(ctx);
int ptid = *parent_tidptr;

bpf_printk("clone_flags == %x, ptid == %x\n", clone_flags, ptid);

런타임 시 이런 오류가 발생합니다.

field KprobeSysClone: program kprobe_sys_clone: load program: permission denied: 2: (61) r7 = *(u32 *)(r1 +0): R1 invalid mem access 'scalar' (7 line(s) omitted)

이에 대해서 검색해 보면,

How to correctly read socket->sk from pt_regs* in ebpf program?
; https://stackoverflow.com/questions/76960866/how-to-correctly-read-socket-sk-from-pt-regs-in-ebpf-program

eBPF Verifier 입장에서는 저 포인터의 타입은 물론, 그것이 포인터인지조차도 알 수 없다고 합니다. 따라서 그런 식으로 포인터의 값을 읽을 수는 없고 별도로 bpf_probe_read_kernel/bpf_probe_read_user 함수를 이용해야 한다고 합니다.

int ptid = 0;
int* parent_tidptr = (int*)PT_REGS_PARM3(ctx);
if (parent_tidptr != NULL)
{
    bpf_probe_read_kernel(&ptid, sizeof(__u32), parent_tidptr);
}




BPF 함수는 매개변수의 수가 5개를 넘을 수 없습니다. 따라서 그 이상의 매개변수가 정의된 경우가 있다면,

// https://github.com/torvalds/linux/blob/ae90f6a6170d7a7a1aa4fddf664fbd093e3023bc/include/linux/syscalls.h#L786

unsigned long ksys_mmap_pgoff(unsigned long addr, unsigned long len,
                  unsigned long prot, unsigned long flags,
                  unsigned long fd, unsigned long pgoff);

SEC("kprobe/ksys_mmap_pgoff") int BPF_KPROBE(ksys_mmap_pgoff, unsigned long addr, unsigned long len,
                                                            unsigned long prot, unsigned long flags,
                                                            unsigned long fd, unsigned long pgoff)
{
    bpf_printk("addr == %x\n", addr);
    bpf_printk("len == %x\n", len);
    bpf_printk("prot == %x\n", prot);
    bpf_printk("flags == %x\n", flags);
    bpf_printk("fd == %x\n", fd);
    bpf_printk("pgoff == %x\n", pgoff);
    return 0;
}

빌드 시 이런 오류가 발생합니다.

/mnt/c/temp/ebpf_sample/basic.c:26:35: error: call to undeclared function '___bpf_kprobe_args6'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
   26 | SEC("kprobe/ksys_mmap_pgoff") int BPF_KPROBE(ksys_mmap_pgoff, unsigned long addr, unsigned long len,
      |                                   ^
/usr/include/bpf/bpf_tracing.h:429:20: note: expanded from macro 'BPF_KPROBE'
  429 |         return ____##name(___bpf_kprobe_args(args));                        \
      |                           ^
/usr/include/bpf/bpf_tracing.h:409:2: note: expanded from macro '___bpf_kprobe_args'
  409 |         ___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
      |         ^
/usr/include/bpf/bpf_helpers.h:165:29: note: expanded from macro '___bpf_apply'
  165 | #define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
      |                             ^
/usr/include/bpf/bpf_helpers.h:162:29: note: expanded from macro '___bpf_concat'
  162 | #define ___bpf_concat(a, b) a ## b
      |                             ^
<scratch space>:36:1: note: expanded from here
   36 | ___bpf_kprobe_args6
      | ^
/mnt/c/temp/ebpf_sample/basic.c:26:63: error: expected expression
   26 | SEC("kprobe/ksys_mmap_pgoff") int BPF_KPROBE(ksys_mmap_pgoff, unsigned long addr, unsigned long len,
      |                                                               ^
/mnt/c/temp/ebpf_sample/basic.c:26:83: error: expected expression
   26 | SEC("kprobe/ksys_mmap_pgoff") int BPF_KPROBE(ksys_mmap_pgoff, unsigned long addr, unsigned long len,
      |                                                                                   ^
/mnt/c/temp/ebpf_sample/basic.c:27:61: error: expected expression
   27 |                                                             unsigned long prot, unsigned long flags,
      |                                                             ^
/mnt/c/temp/ebpf_sample/basic.c:27:81: error: expected expression
   27 |                                                             unsigned long prot, unsigned long flags,
      |                                                                                 ^
/mnt/c/temp/ebpf_sample/basic.c:28:61: error: expected expression
   28 |                                                             unsigned long fd, unsigned long pgoff)
      |                                                             ^
/mnt/c/temp/ebpf_sample/basic.c:28:79: error: expected expression
   28 |                                                             unsigned long fd, unsigned long pgoff)
      |                                                                               ^
7 errors generated.
Error: compile: exit status 1
exit status 1
main.go:4: running "go": exit status 1

___bpf_kprobe_args6 정의가 없다는 것인데, 실제로 제가 빌드하는 환경에 있는 "/usr/include/bpf/bpf_tracing.h" 파일에 그 정의를 찾아볼 수 없습니다. 반면 libbpf 소스코드를 보면,

// https://github.com/libbpf/libbpf/blob/master/src/bpf_tracing.h

// ...[생략]...

#define ___bpf_kprobe_args6(x, args...) ___bpf_kprobe_args5(args), (unsigned long long)PT_REGS_PARM6(ctx)
#define ___bpf_kprobe_args7(x, args...) ___bpf_kprobe_args6(args), (unsigned long long)PT_REGS_PARM7(ctx)
#define ___bpf_kprobe_args8(x, args...) ___bpf_kprobe_args7(args), (unsigned long long)PT_REGS_PARM8(ctx)

이렇게 6 ~ 8개의 인자를 처리할 수 있는 것처럼 나오는데, 이게 버전에 따라 지원을 한다는 것인지는 잘 모르겠습니다. 어쨌든, (특별한 방법이 있는 것인지는 알 수 없으나) 5개를 초과해서는 매개변수 정의가 안 됩니다. 반면, 아키텍처 구조를 잘 알고 있다면 추출하는 것이 가능한 것 같은데요, 이에 관해서는 다음의 글을 참고하세요. ^^

Extracting kprobe parameters in eBPF
; https://eyakubovich.github.io/2022-04-19-ebpf-kprobe-params/

결국 저런 방법까지 동원할 생각이 없다면 5개 이하로만 매개변수를 정의해야 합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/19/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13743정성태9/26/20246412닷넷: 2298. C# - Console 프로젝트에서의 await 대상으로 Main 스레드 활용하는 방법 [1]
13742정성태9/26/20246701닷넷: 2297. C# - ssh-keygen으로 생성한 ecdsa 유형의 Public Key 파일 해석 [1]파일 다운로드1
13741정성태9/25/20245882디버깅 기술: 202. windbg - ASP.NET MVC Web Application (.NET Framework) 응용 프로그램의 덤프 분석 시 요령
13740정성태9/24/20245741기타: 86. RSA 공개키 등의 modulus 값에 0x00 선행 바이트가 있는 이유(ASN.1 인코딩)
13739정성태9/24/20245887닷넷: 2297. C# - ssh-keygen으로 생성한 Public Key 파일 해석과 fingerprint 값(md5, sha256) 생성 [1]파일 다운로드1
13738정성태9/22/20245613C/C++: 174. C/C++ - 윈도우 운영체제에서의 file descriptor, FILE*파일 다운로드1
13737정성태9/21/20245974개발 환경 구성: 727. Visual C++ - 리눅스 프로젝트를 위한 빌드 서버의 msbuild 구성
13736정성태9/20/20245972오류 유형: 923. Visual Studio Code - Could not establish connection to "...": Port forwarding is disabled.
13735정성태9/20/20246049개발 환경 구성: 726. ARM 플랫폼용 Visual C++ 리눅스 프로젝트 빌드
13734정성태9/19/20245750개발 환경 구성: 725. ssh를 이용한 원격 docker 서비스 사용
13733정성태9/19/20246087VS.NET IDE: 194. Visual Studio - Cross Platform / "Authentication Type: Private Key"로 접속하는 방법
13732정성태9/17/20246122개발 환경 구성: 724. ARM + docker 환경에서 .NET 8 설치
13731정성태9/15/20246711개발 환경 구성: 723. C# / Visual C++ - Control Flow Guard (CFG) 활성화 [1]파일 다운로드2
13730정성태9/10/20246373오류 유형: 922. docker - RULE_APPEND failed (No such file or directory): rule in chain DOCKER
13729정성태9/9/20247120C/C++: 173. Windows / C++ - AllocConsole로 할당한 콘솔과 CRT 함수 연동 [1]파일 다운로드1
13728정성태9/7/20246932C/C++: 172. Windows - C 런타임에서 STARTUPINFO의 cbReserved2, lpReserved2 멤버를 사용하는 이유파일 다운로드1
13727정성태9/6/20247469개발 환경 구성: 722. ARM 플랫폼 빌드를 위한 미니 PC(?) - Khadas VIM4 [1]
13726정성태9/5/20247383C/C++: 171. C/C++ - 윈도우 운영체제에서의 file descriptor와 HANDLE파일 다운로드1
13725정성태9/4/20246138디버깅 기술: 201. WinDbg - sos threads 명령어 실행 시 "Failed to request ThreadStore"
13724정성태9/3/20247998닷넷: 2296. Win32/C# - 자식 프로세스로 HANDLE 상속파일 다운로드1
13723정성태9/2/20248256C/C++: 170. Windows - STARTUPINFO의 cbReserved2, lpReserved2 멤버 사용자 정의파일 다운로드2
13722정성태9/2/20245990C/C++: 169. C/C++ - CRT(C Runtime) 함수에 의존성이 없는 프로젝트 생성
13721정성태8/30/20246024C/C++: 168. Visual C++ CRT(C Runtime DLL: msvcr...dll)에 대한 의존성 제거 - 두 번째 이야기
13720정성태8/29/20246189VS.NET IDE: 193. C# - Visual Studio의 자식 프로세스 디버깅
13719정성태8/28/20246332Linux: 79. C++ - pthread_mutexattr_destroy가 없다면 메모리 누수가 발생할까요?
13718정성태8/27/20247414오류 유형: 921. Visual C++ - error C1083: Cannot open include file: 'float.h': No such file or directory [2]
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...