실행 중인 실행 파일 덮어쓰기 또는 하나 이상의 실행 프로그램에서 사용 중인 공유 라이브러리(.so) 파일 덮어쓰기에 대한 질문이 있습니다.
과거에는 실행 중인 실행 파일을 덮어쓰는 것이 명백한 이유로 작동하지 않았습니다. 이 상황을 다루는 특정 errno 값 ETXTBSY도 있습니다.
그러나 오랫동안 나는 실수로 실행 중인 실행 파일을 덮어쓰려고 하면(예: 실행 중인 빌드의 마지막 단계를 실행하여) 작동한다는 것을 알아냈습니다 cc -o exefile
!exefile
그래서 제 질문은 이것이 어떻게 작동하는지, 어디에든 문서화되어 있는지, 그리고 그것에 의존해도 안전한지입니다.
ld
이 경우 오류를 제거하기 위해 누군가 출력 파일의 링크를 해제하고 새 파일을 생성하도록 조정한 것 같습니다 . 이 작업을 항상 수행하는지, 아니면 필요할 때만 수행하는지(즉, 기존 파일을 덮어쓰려고 시도하고 ETXTBSY를 만난 후) 확실하지 않습니다. ld
매뉴얼 페이지에는 이에 대한 언급이 없습니다 . (사람들이 ld
지금 하드링크를 깨거나 파일 소유권을 변경할 가능성 등에 대해 왜 불평하지 않는지 궁금합니다 .)
cc
부록: 이 질문은 / 에 관한 것이 아닙니다 ld
(결국 답변의 중요한 부분이 되었지만). 질문은 실제로 "왜 더 이상 ETXTBSY를 볼 수 없습니까? 이것이 여전히 버그입니까?"입니다. 예, 여전히 버그입니다. 실제로는 드문 버그입니다. (방금 내 질문에 게시한 명확한 답변도 참조하세요.)
답변1
이는 커널에 따라 다르며 일부 커널의 경우 실행 파일 유형에 따라 달라질 수 있지만 모든 최신 시스템은 ETXTBSY("텍스트 파일busy") 쓰기를 위해 실행 중인 실행 파일을 열려고 하거나 쓰기 위해 열린 파일을 실행하려고 하면 설명서에 나와 있습니다.BSD에서는 항상 이런 일이 있었습니다., 하지만초기 솔라리스에서는 그렇지 않았습니다.(이후 버전에서는 이 보호 기능을 구현했습니다.), 내 기억과 일치합니다. Linux에서는 항상 그렇습니다.최소 1.0.
실행 파일에 적용되는 것은 동적 라이브러리에 적용되거나 적용되지 않을 수 있습니다. 동적 라이브러리를 덮어쓰면 실행 파일을 덮어쓰는 것과 정확히 같은 문제가 발생합니다. 명령은 완전히 다른 내용을 포함할 수 있는 새 파일의 동일한 이전 주소에서 갑자기 로드됩니다. 그러나 실제로 모든 곳에서 그런 것은 아닙니다. 특히, Linux에서 프로그램은 open
모든 데이터 파일과 동일한 플래그를 사용하여 내부적으로 동적 라이브러리를 열기 위해 시스템 호출을 호출하며, Linux에서는 실행 중인 프로세스가 언제 코드를 로드하더라도 라이브러리 파일을 재정의할 수 있습니다.
대부분의 커널에서는 파일을 열거나 쓰는 동안 파일을 삭제하고 이름을 바꿀 수 있는 것과 마찬가지로 실행 중에 파일을 삭제하고 이름을 바꿀 수 있습니다. 열린 파일과 마찬가지로 실행 중에 삭제된 파일은 사용 중인 동안, 즉 실행 파일의 마지막 인스턴스가 종료될 때까지 실제로 저장 매체에서 삭제되지 않습니다. Linux 및 *BSD에서는 이를 허용하지만 Solaris 및 HP-UX에서는 허용하지 않습니다.
파일을 삭제하고 같은 이름으로 새 파일을 작성하는 것은 매우 안전합니다. 로드할 코드와 해당 코드가 포함된 열려 있는(또는 실행하는) 파일 간의 연결은 파일 이름이 아닌 파일 설명자를 통해 이루어집니다. rename
임시 파일에 쓴 다음 해당 파일을 제자리로 이동(기존 대상 파일을 소스 파일로 원자적으로 바꾸는 시스템 호출)하여 이 작업을 자동으로 수행 할 수 있다는 추가 이점이 있습니다 . 유효하지 않거나 부분적으로 작성된 실행 파일을 일시적으로 배치하지 않기 때문에 삭제한 다음 쓰기 위해 여는 것보다 훨씬 낫습니다.
cc
출력 파일을 덮어 쓰는지, 아니면 삭제하고 새 파일을 생성하는지 여부 ld
는 구현에 따라 다릅니다. GCC(최소한 최신 버전)와 Clang 모두 이 작업을 수행합니다. 두 경우 모두 unlink
대상(존재하는 경우)을 호출하여 open
새 파일을 생성합니다. (왜 임시 쓰기 후 이름 바꾸기를 수행하지 않는지 궁금합니다.)
보호 조치를 제외하고는 이 동작에 의존하지 않는 것이 좋습니다. 왜냐하면 이 동작은 모든 시스템에서 작동하지 않으며(아마도 실행 파일에 대해서는 모든 최신 시스템에서 작동하지만 공유 라이브러리에서는 작동하지 않을 것임), 일반적인 툴체인에서는 작동하지 않기 때문입니다. 가능한 최선의 방법으로요. 빌드 스크립트에서 항상 임시 파일 아래에 파일을 생성한 다음 기본 도구가 이 작업을 수행할지 모르는 경우 해당 위치로 이동합니다.
답변2
파일 핸들이 열려 있는 동안 파일이 삭제되면 일반적으로 마지막 파일 핸들이 닫히면 파일이 삭제 대상으로 표시됩니다. 이 시점에서 파일은 더 이상 디렉터리 목록에 표시되지 않지만(예를 들어) lsof
출력에는 삭제된 것으로 표시되었지만 사용 중인 파일로 표시됩니다 .
lsof
간결성과 명확성을 위해 다음 내용이 잘렸습니다.
$ cat - >> foo &
[1] 30779
$ lsof | grep 30779
cat 30779 ghoti 1w REG 252,0 0 262155 /home/ghoti/foo
[1]+ Stopped cat - >> foo
$ rm foo
$ ls foo
ls: cannot access 'foo': No such file or directory
$ lsof | grep 30779
cat 30779 ghoti 1w REG 252,0 0 262155 /home/ghoti/foo (deleted)
그렇다면 실행 중인 fg
(삭제된) 파일에 계속 쓸 수 있습니다. (실제로 원하는 경우 파일을 복구할 수 있습니다.foo
cat
/proc/30779/fd/1
cat
파일이 아직 열려 있지만).
답변3
내 자신의 질문에 대답하려면(또는 실제로 질문을 조금 명확히 한 다음 명확한 질문에 대한 답을 설명하십시오):
내 질문은 "다시는 ETXTBSY를 볼 수 없습니다. 여전히 버그인가요? 아니면 최신 커널을 사용하면 실행 중인 실행 파일을 불평하지 않고 (어떻게든) 실행하기 위해 삭제하지 않고 실행 중인 실행 파일을 덮어쓸 수 있습니까?"입니다.
나는 실행 중인 실행 파일에 쓸 때 최신 커널이 멋진 쓰기 시 복사 의미 체계를 구현하고 있다고 심각하게 의심하기 시작했습니다.
그러나 그것은 진실이 아니다. ETXTBSY는 여전히 확실히 버그입니다.
내 혼란에 대한 대답은 실행 중인 실행 파일에 대한 쓰기가 실제로 거의 발생하지 않는다는 것입니다. 새 실행 파일을 제자리로 옮기면(그리고 기존 실행 파일은 여전히 실행 중임) 실제로 덮어쓰는 일은 거의 없으며 항상 제거하고 교체하게 됩니다. 사용중이시라면 mv
삭제 후 교체해 드립니다. install
, 또는 이와 유사한 것을 사용하는 경우 dpkg -i
삭제하고 교체하게 됩니다. 어떤 이유로든 cp
이를 사용하려고 하는 경우에만 덮어쓰려고 하며, 이전 버전이 아직 실행 중인 경우 ETXTBSY가 발생할 위험이 있습니다.
그런 다음 조용한 변경 덕분에 실행 중인 실행 파일 위에서 작업을 ld
시도하는 것도 이제 "제거 및 교체" 범주에 속합니다.cc -o
답변4
귀하의 추측은 정확합니다. ld는 사용할 수 있는 새 파일에 기록합니다 ls -li
. 이 -i
옵션은 각 컴파일 후에 변경되는 inode 번호를 표시합니다. 나는 많은 사람들이 하드 링크를 깨는 것에 관심이 없다고 생각합니다. 프로그램은 일반적으로 컴파일되지 않으므로 실행 파일은 ld에 의해 최종 목적지로 작성됩니다.