Microsoft MVP성태의 닷넷 이야기
Linux: 101. eBPF 함수의 인자를 다루는 방법 [링크 복사], [링크+제목 복사],
조회: 5372
글쓴 사람
정성태 (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

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  26  [27]  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13262정성태2/15/202313474디버깅 기술: 191. dnSpy를 이용한 (소스 코드가 없는) 닷넷 응용 프로그램 디버깅 방법 [1]
13261정성태2/15/202312955Windows: 224. Visual Studio - 영문 폰트가 Fullwidth Latin Character로 바뀌는 문제
13260정성태2/14/202312201오류 유형: 847. ilasm.exe 컴파일 오류 - error : syntax error at token '-' in ... -inf
13259정성태2/14/202312148.NET Framework: 2095. C# - .NET5부터 도입된 CollectionsMarshal
13258정성태2/13/202312897오류 유형: 846. .NET Framework 4.8 Developer Pack 설치 실패 - 0x81f40001
13257정성태2/13/202312316.NET Framework: 2094. C# - Job에 Process 포함하는 방법 [1]파일 다운로드1
13256정성태2/10/202312455개발 환경 구성: 665. WSL 2의 네트워크 통신 방법 - 두 번째 이야기
13255정성태2/10/202312665오류 유형: 845. gihub - windows2022 이미지에서 .NET Framework 4.5.2 미만의 프로젝트에 대한 빌드 오류
13254정성태2/10/202312551Windows: 223. (WMI 쿼리를 위한) PowerShell 문자열 escape 처리
13253정성태2/9/202313700Windows: 222. C# - 다른 윈도우 프로그램이 실행되었음을 인식하는 방법파일 다운로드1
13252정성태2/9/202311588오류 유형: 844. ssh로 명령어 수행 시 멈춤 현상
13251정성태2/8/202312179스크립트: 44. 파이썬의 3가지 스레드 ID
13250정성태2/8/202313763오류 유형: 843. System.InvalidOperationException - Unable to configure HTTPS endpoint
13249정성태2/7/202314363오류 유형: 842. 리눅스 - You must wait longer to change your password
13248정성태2/7/202311036오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
13247정성태2/7/202312596VS.NET IDE: 180. Visual Studio - 닷넷 소스 코드 디버깅 중 "Decompile source code"가 동작하는 않는 문제
13246정성태2/6/202312427개발 환경 구성: 664. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 - 두 번째 이야기
13245정성태2/6/202312965.NET Framework: 2093. C# - PKCS#8 PEM 파일을 이용한 RSA 개인키/공개키 설정 방법파일 다운로드1
13244정성태2/5/202312082VS.NET IDE: 179. Visual Studio - External Tools에 Shell 내장 명령어 등록
13243정성태2/5/202313210디버깅 기술: 190. windbg - Win32 API 호출 시점에 BP 거는 방법 [1]
13242정성태2/4/202312064디버깅 기술: 189. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.UnauthorizedAccessException
13241정성태2/3/202310649디버깅 기술: 188. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.IO.FileNotFoundException
13240정성태2/1/202311328디버깅 기술: 187. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.Web.HttpException
13239정성태2/1/202310693디버깅 기술: 186. C# - CacheDependency의 숨겨진 예외 - System.Web.HttpException
13238정성태1/31/202314976.NET Framework: 2092. IIS 웹 사이트를 TLS 1.2 또는 TLS 1.3 프로토콜로만 운영하는 방법
13237정성태1/30/202314282.NET Framework: 2091. C# - 웹 사이트가 어떤 버전의 TLS/SSL을 지원하는지 확인하는 방법
... 16  17  18  19  20  21  22  23  24  25  26  [27]  28  29  30  ...